Dark mode

Dark mode

There are 0 results matching

article card image dark article card image light

Published by · Jun 25, 2024 tools · 2 mins read

Introducing: macOS JAMF Offboarding Tool

Offboarding macOS Devices from JAMF in Bulk using the JAMF API with a bash script ...

See More
article card image dark article card image light

Published by · Jun 25, 2024 tools · 2 mins read

Introducing: Microsoft Cloud License Automation Tool - Part 1

Automating Microsoft Cloud License Assignment and Reporting with PowerShell and Slack for Enterprise Mobility and Security E3 ...

See More
article card image dark article card image light

Published by · Jun 18, 2024 tools · 2 mins read

Introducing: Configuration Manager Set Implicit Uninstall Flag Tool

Setting Configuration Manager ConfigMgr Implicit Uninstall Flag with PowerShell for Required Application Deployments ...

See More
article card image dark article card image light

Published by · Jun 11, 2024 configmgr · 2 mins read

Configuration Manager Next Maintenance Window SQL Function

Get Next Configuration Manager Maintenance Window from a Schedule Token with Offset Days using an SQL Function. ...

See More
article card image dark article card image light

Published by · Jun 3, 2024 tools · 2 mins read

Introducing: Windows User Rights Assignment Tool - Part 3

Add, Remove, or Replace Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · May 28, 2024 tools · 2 mins read

Introducing: Windows User Rights Assignment Tool - Part 2

Get and Report Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · May 22, 2024 tools · 1 mins read

Introducing: Windows User Rights Assignment Tool - Part 1

Get Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · Apr 11, 2024 tools · 2 mins read

Introducing: Intune Linux Onboarding Tool

Onboard Ubuntu Linux devices to Microsoft Intune using a bash script. Installs prerequisites and starts the user-driven enrollment. ...

See More
article card image dark article card image light

Published by · Apr 11, 2024 tools · 2 mins read

Introducing: Intune macOS Onboarding Tool

Onboard macOS devices to Microsoft Intune using a bash script that initiates the process. Optionally, the script converts mobile accounts, resets the FileVault key, and removes ...

See More
article card image dark article card image light

Published by · Jan 23, 2024 tools · 3 mins read

Introducing: Intune Device Renaming Tool

Rename Intune Devices by setting a Prefix or using a User Attribute as Prefix. Supports Windows, macOS, and Linux ...

See More
article card image dark article card image light

Published by · Dec 8, 2023 intune · 5 mins read

Intune Logs: A Deep Dive into Locations, Interpretation, and Configuration

A Comprehensive Guide to Locations, Interpretation, and Configuration of Intune Logs ...

See More
article card image dark article card image light

Published by · Aug 14, 2023 configmgr · 2 mins read

Configuration Manager Console Extension to show Device Collection Membership with Console Builder

Use the Configuration Manager Console Builder, to add Collection Membership View to the Device Node ...

See More
article card image dark article card image light

Published by · Aug 3, 2023 tools · 3 mins read

Introducing: Configuration Manager SSRS Dashboards

A Configuration Manager Dashboards solution with Reports for Software Updates, Bitlocker and more ...

See More
article card image dark article card image light

Published by · Aug 3, 2023 tools · 2 mins read

Introducing: PowerShell WMI Management Toolkit Module

Streamline your WMI Namespace, Class, and Instance Management with our PowerShell Module ...

See More
article card image dark article card image light

Published by · Jul 14, 2023 configmgr · 1 mins read

Configuration Manager detailed, filterable Port Documentation

Configuration Manager detailed, filterable port documentation as an excel document ...

See More
article card image dark article card image light

Published by · Jul 14, 2023 configmgr · 3 mins read

Configuration Manager PXE TFTP Window Size Bug

Configuration Manager TFTP Block Size and TFTP Window Size Correct Configuration ...

See More
article card image dark article card image light

Published by · Jun 18, 2023 tools · 4 mins read

Introducing: Configuration Manager Client Cache Cleanup Tool

Cleaning the Configuration Manager Client Cache the Right Way with PowerShell and Configuration Baselines ...

See More
article card image dark article card image light

Published by · Jun 18, 2023 tools · 2 mins read

Introducing: Windows Cache Cleanup Tool

Cleaning Windows and Configuration Manager Caches for Configuration Manager Build and Capture Task Sequence or Standalone Use ...

See More
article card image dark article card image light

Published by · Jun 17, 2023 tools · 1 mins read

Introducing: Windows Update Database Reinitialization Tool

Proactively repair corrupted Windows Update Database with Powershell and Configuration Manager ...

See More
article card image dark article card image light

Published by · Mar 31, 2023 tools · 3 mins read

Introducing: Configuration Manager SQL Products Reporting

A Complete SQL Products reporting solution using Configuration Manager ...

See More
article card image dark article card image light

Published by · Jan 28, 2023 configmgr · 1 mins read

Application Detection Method using the Configuration Manager Application Version

Replace hardcoded application version in scripts, with the Configuration Manager Application Version ...

See More
article card image dark article card image light

Published by · Jan 28, 2023 tools · 3 mins read

Introducing: Certificate Management Toolkit

Managing Certificates with Configuration Manager and PowerShell by using just the Public Key ...

See More
article card image dark article card image light

Published by · Jan 7, 2019 reports · 2 mins read

Configuration Manager Device Boundary and Network Information Report

List Device Boundaries and Network Information with Configuration Manager ...

See More
article card image dark article card image light

Published by · Sep 9, 1980 help · 5 mins read

MEM.Zone Blog Publishing Documentation

Publishing Documentation for MEM.Zone ...

See More

We couldn’t find anything related to

“SCCM”

BLOG / tools zone

Introducing: Intune Device Renaming Tool

Published by Vilcu Paul · Jan 23, 2024 · 3 mins read
article card image dark article card image light

Quick Summary

Microsoft Intune provides the capability of renaming device names via the Intune Portal, and also supports bulk renaming, but doesn’t support Linux devices.

We recently encountered a scenario where we needed macOS and Linux devices on-boarded to Intune via the Company Portal to respect a set naming convention using a specific UserAttribute.

To solve this issue, we created a PowerShell script that renames one or more devices and can target a specific Operating System. It supports setting a Prefix or using a User Attribute as prefix, automatic trimming of the name for Windows devices, and full logging.

Prerequisites

What You Need

  • Microsoft Entra Application registration with MS Graph API permissions. See a great example from Laura Kokkarinen.
  • Microsoft Entra synched extension attribute with the value you want to use as prefix (optional).
  • PowerShell Script

Supported Devices

  • Android Enterprise:
    • Corporate-owned work profiles
    • Dedicated devices
    • Fully managed
  • iOS/iPadOS supervised devices with iOS 9.3 and later
  • macOS 10 - Corporate-owned devices
  • Windows - Corporate-owned devices
  • Linux - Corporate-owned devices
  • Corporate-owned co-managed devices that are Microsoft Entra joined
Notes

All rights needed to run this script will get prompted the first time you run it with your privilege account on your Tenant.
You can review the necessary rights and asses if this is acceptable for you.

Notes

  • The rename action does not change the Management Name in the Intune Portal or the Device Name in Company Portal.
  • Hybrid Microsoft Entra devices are not supported, you can use a local device rename script for those and the changes will be propagated everywhere.


Recommendations

  • Run the script with -WhatIf, -Confim and Verbose first to verify that everything works as intended.
  • Use multiple test devices to validate your configuration.
  • History is available in the Endpoint Management event log or %SystemRoot%\Logs\Rename-IntuneDevice\ folder.
  • An Azure Automation Account is recommended for automation, running at a set interval.
Notes

Although the script works in an environment with 15.000 devices, it cannot account for all edge cases. Please test thoroughly before using it in production.


Parameters

TenantID

This parameter specifies the tenant ID.

ApplicationID

This parameter specifies the application (client) ID.

ApplicationSecret

This parameter specifies the application (client) secret.

DeviceName

This parameter specifies the device name to be processed.

Defaults to All.

DeviceOS

This parameter specifies the device OS to be processed.

  • Windows
  • macOS
  • Linux

Defaults to All.

Prefix

This parameter specifies the prefix to use.

Defaults to INTUNE.

PrefixFromUserAttribute

This parameter specifies the user attribute to query and use as the prefix. If no user attribute exists, the device is skipped.


Notes

  • Prefix is always truncated to 6 characters and converted to UPPERCASE.
  • Suffix is always the device serial number converted to UPPERCASE and cannot be changed.
  • Serial Number is always truncated so the Device Name doesn’t exceed 15 characters for Windows OS.
  • PrefixFromUserAttribute output is always truncated to 6 characters and converted to UPPERCASE.


Examples

Example 1

$ConnectionParameters = [hashtable]@{
    TenantID          = '10000000-1000-1000-1000-100000000000'
    ApplicationID     = '12345678-1234-1234-1234-123456789101'
    ApplicationSecret = 'xa21kkjkash3asjjsakdkjdhk'
}
Rename-IntuneDevice.ps1 @ConnectionParameters -DeviceName 'IntuneDevice001' -WhatIf -Verbose

Example 2

$ConnectionParameters = [hashtable]@{
    TenantID          = '10000000-1000-1000-1000-100000000000'
    ApplicationID     = '12345678-1234-1234-1234-123456789101'
    ApplicationSecret = 'xa21kkjkash3asjjsakdkjdhk'
}
Rename-IntuneDevice.ps1 @ConnectionParameters -DeviceOS 'Windows' -Prefix 'TAG'

Example 3

$ConnectionParameters = [hashtable]@{
    TenantID          = '10000000-1000-1000-1000-100000000000'
    ApplicationID     = '12345678-1234-1234-1234-123456789101'
    ApplicationSecret = 'xa21kkjkash3asjjsakdkjdhk'
}
Rename-IntuneDevice.ps1 @ConnectionParameters -DeviceOS 'Windows' -PrefixFromUserAttribute 'extension_16db5763993a4e819bc7dd1824184322_msDS_cloudExtensionAttribute5'

Example 4

$ConnectionParameters = [hashtable]@{
    TenantID          = '10000000-1000-1000-1000-100000000000'
    ApplicationID     = '12345678-1234-1234-1234-123456789101'
    ApplicationSecret = 'xa21kkjkash3asjjsakdkjdhk'
}
Rename-IntuneDevice.ps1 @ConnectionParameters -Confirm

Preview

article card image windows-script-eventlog
Script event log
article card image windows-script-file-log
Script file log

Code

   1<#
   2.SYNOPSIS
   3    Renames an Intune device.
   4.DESCRIPTION
   5    Renames an Intune device according to a specified naming convention.
   6.PARAMETER TenantID
   7    Specifies the tenant ID.
   8.PARAMETER ApplicationID
   9    Specifies the application ID.
  10.PARAMETER ApplicationSecret
  11    Specifies the application secret.
  12.PARAMETER DeviceName
  13    Specifies the device name to be processed.
  14    Default is: 'All'.
  15.PARAMETER DeviceOS
  16    Specifies the device OS to be processed
  17    Valid values are: 'Windows', 'macOS', 'iOS/iPadOS', 'Linux', 'Android', 'All',  .
  18    Default is: 'All'.
  19.PARAMETER Prefix
  20    Specifies the prefix to be used. Please note that it will be truncated to 6 characters and converted to UPPERCASE.
  21    If this parameter is used, the PrefixFromUserAttribute parameter will be ignored.
  22    Default is: 'INTUNE'.
  23.PARAMETER PrefixFromUserAttribute
  24    Specifies the user attribute to be used queried and used as prefix. The result will be truncated to 6 characters.
  25    If this parameter is used, the Prefix parameter will be ignored.
  26.EXAMPLE
  27    Rename-IntuneDevice.ps1 -TenantID $TenantID -ApplicationID $ApplicationID -ApplicationSecret $ApplicationSecret -DeviceName 'IntuneDevice001' -WhatIf -Verbose
  28.EXAMPLE
  29    Rename-IntuneDevice.ps1 -TenantID $TenantID -ApplicationID $ApplicationID -ApplicationSecret $ApplicationSecret -DeviceOS 'macOS' -Prefix 'TAG'
  30.EXAMPLE
  31    Rename-IntuneDevice.ps1 -TenantID $TenantID -ApplicationID $ApplicationID -ApplicationSecret $ApplicationSecret -DeviceOS 'Windows' -PrefixFromUserAttribute 'extension_11db5763783a4e822bd6dsd1826184312_msDS_cloudExtensionAttribute66'
  32.EXAMPLE
  33    Rename-IntuneDevice.ps1 -TenantID $TenantID -ApplicationID $ApplicationID -ApplicationSecret $ApplicationSecret -DeviceOS 'Android' -Confirm
  34.INPUTS
  35    None.
  36.OUTPUTS
  37    None.
  38.NOTES
  39    Created by Ferry Bodijn
  40    Rewritten by Ioan Popovici to add parameters, improve logging and simplify the script. All other functionality remains the same.
  41    v1.0.0 - 2021-09-01
  42
  43    Supports WhatIf and Confirm, see links below for more information.
  44.LINK
  45    https://MEMZ.one/Rename-IntuneDevice
  46.LINK
  47    https://MEMZ.one/Rename-IntuneDevice-CHANGELOG
  48.LINK
  49    https://MEMZ.one/Rename-IntuneDevice-GIT
  50.LINK
  51    https://MEM.Zone/ISSUES
  52.LINK
  53    https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-shouldprocess?view=powershell-7.3#using--whatif
  54.COMPONENT
  55    MSGraph
  56.FUNCTIONALITY
  57    Renames device in Intune.
  58#>
  59
  60##*=============================================
  61##* VARIABLE DECLARATION
  62##*=============================================
  63#region VariableDeclaration
  64
  65## Set script requirements
  66#Requires -Version 5.0
  67
  68## Get script parameters
  69[CmdletBinding(SupportsShouldProcess=$true, DefaultParameterSetName = 'Custom')]
  70Param (
  71    [Parameter(Mandatory = $true, ParameterSetName = 'Custom', HelpMessage = 'Specify the tenant ID', Position = 0)]
  72    [Parameter(Mandatory = $true, ParameterSetName = 'UserAttribute', HelpMessage = 'Enter the tenant ID', Position = 0)]
  73    [ValidateNotNullorEmpty()]
  74    [Alias('Tenant')]
  75    [string]$TenantID,
  76    [Parameter(Mandatory = $true, ParameterSetName = 'Custom', HelpMessage = 'Specify the Application (Client) ID to use.', Position = 1)]
  77    [Parameter(Mandatory = $true, ParameterSetName = 'UserAttribute', HelpMessage = 'Specify the Application (Client) ID to use.', Position = 1)]
  78    [ValidateNotNullorEmpty()]
  79    [Alias('ApplicationClientID')]
  80    [string]$ClientID,
  81    [Parameter(Mandatory = $true, ParameterSetName = 'Custom', HelpMessage = 'Specify the Application (Client) Secret to use.', Position = 2)]
  82    [Parameter(Mandatory = $true, ParameterSetName = 'UserAttribute', HelpMessage = 'Specify the Application (Client) Secret to use.', Position = 2)]
  83    [ValidateNotNullorEmpty()]
  84    [Alias('ApplicationClientSecret')]
  85    [string]$ClientSecret,
  86    [Parameter(Mandatory = $false, ParameterSetName = 'Custom', HelpMessage = 'Specify the device name to be processed. Supports wildcard characters. Default is: All', Position = 3)]
  87    [Parameter(Mandatory = $false, ParameterSetName = 'UserAttribute', HelpMessage = 'Specify the device name to be processed. Supports wildcard characters. Default is: All', Position = 3)]
  88    [ValidateNotNullorEmpty()]
  89    [Alias('Device')]
  90    [string]$DeviceName = 'All',
  91    [Parameter(Mandatory = $false, ParameterSetName = 'Custom', HelpMessage = 'Specify the device OS to be processed. Valid values are: Windows, macOS, Linux, All', Position = 4)]
  92    [Parameter(Mandatory = $false, ParameterSetName = 'UserAttribute', HelpMessage = 'Specify the device OS to be processed. Valid values are: Windows, macOS, Linux, Android, iOS/iPadOS, All', Position = 4)]
  93    [ValidateSet('Windows', 'macOS', 'Linux', 'Android', 'iOS/iPadOS', 'All')]
  94    [string]$DeviceOS = 'All',
  95    [Parameter(Mandatory = $false, ParameterSetName = 'Custom', HelpMessage = 'Specify the prefix to be used. Default isL INTUNE', Position = 5)]
  96    [ValidateNotNullorEmpty()]
  97    [string]$Prefix = 'INTUNE',
  98    [Parameter(Mandatory = $true, ParameterSetName = 'UserAttribute', HelpMessage = 'Specify the user attribute to be used queried and used as prefix. The result will be truncated to 6 characters.', Position = 5)]
  99    [ValidateNotNullorEmpty()]
 100    [Alias('UserAttribute')]
 101    [string]$PrefixFromUserAttribute
 102)
 103
 104## Get script path and name
 105[string]$ScriptName     = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Definition)
 106[string]$ScriptFullName = [System.IO.Path]::GetFullPath($MyInvocation.MyCommand.Definition)
 107
 108## Get Show-Progress steps
 109$ProgressSteps = $(([System.Management.Automation.PsParser]::Tokenize($(Get-Content -Path $ScriptFullName), [ref]$null) | Where-Object { $_.Type -eq 'Command' -and $_.Content -eq 'Show-Progress' }).Count)
 110$ForEachSteps  = $(([System.Management.Automation.PsParser]::Tokenize($(Get-Content -Path $ScriptFullName), [ref]$null) | Where-Object { $_.Type -eq 'Keyword' -and $_.Content -eq 'ForEach' }).Count)
 111
 112## Set Show-Progress steps
 113$script:Steps = $ProgressSteps - $ForEachSteps
 114$script:Step  = 1
 115
 116## Set script global variables
 117$script:LoggingOptions   = @('EventLog', 'File', 'Host')
 118$script:LogName          = 'Endpoint Management'
 119$script:LogSource        = $ScriptName
 120$script:LogDebugMessages = $false
 121$script:LogFileDirectory = If ($LogPath) { Join-Path -Path $LogPath -ChildPath $script:LogName } Else { $(Join-Path -Path $Env:WinDir -ChildPath $('\Logs\' + $script:LogName)) }
 122
 123## Initialize script variables
 124If (-not $PSBoundParameters['DeviceName']) { $DeviceName = 'All' }
 125#  Build supported operating systems array
 126[string[]]$SupportedOperatingSystems = @('Windows', 'macOS', 'iOS/iPadOS', 'Linux', 'Android (Corporate-owned work profile)', 'Android (fully managed)', 'Android (dedicated)')
 127
 128#endregion
 129##*=============================================
 130##* END VARIABLE DECLARATION
 131##*=============================================
 132
 133##*=============================================
 134##* FUNCTION LISTINGS
 135##*=============================================
 136#region FunctionListings
 137
 138#region Function Resolve-Error
 139Function Resolve-Error {
 140<#
 141.SYNOPSIS
 142    Enumerate error record details.
 143.DESCRIPTION
 144    Enumerate an error record, or a collection of error record, properties. By default, the details for the last error will be enumerated.
 145.PARAMETER ErrorRecord
 146    The error record to resolve. The default error record is the latest one: $global:Error[0]. This parameter will also accept an array of error records.
 147.PARAMETER Property
 148    The list of properties to display from the error record. Use "*" to display all properties.
 149    Default list of error properties is: Message, FullyQualifiedErrorId, ScriptStackTrace, PositionMessage, InnerException
 150.PARAMETER GetErrorRecord
 151    Get error record details as represented by $_.
 152.PARAMETER GetErrorInvocation
 153    Get error record invocation information as represented by $_.InvocationInfo.
 154.PARAMETER GetErrorException
 155    Get error record exception details as represented by $_.Exception.
 156.PARAMETER GetErrorInnerException
 157    Get error record inner exception details as represented by $_.Exception.InnerException. Will retrieve all inner exceptions if there is more than one.
 158.EXAMPLE
 159    Resolve-Error
 160.EXAMPLE
 161    Resolve-Error -Property *
 162.EXAMPLE
 163    Resolve-Error -Property InnerException
 164.EXAMPLE
 165    Resolve-Error -GetErrorInvocation:$false
 166.NOTES
 167    Unmodified version of the PADT error resolving cmdlet. I did not write the original cmdlet, please do not credit me for it!
 168.LINK
 169    https://psappdeploytoolkit.com
 170#>
 171    [CmdletBinding()]
 172    Param (
 173        [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
 174        [AllowEmptyCollection()]
 175        [array]$ErrorRecord,
 176        [Parameter(Mandatory = $false, Position = 1)]
 177        [ValidateNotNullorEmpty()]
 178        [string[]]$Property = ('Message', 'InnerException', 'FullyQualifiedErrorId', 'ScriptStackTrace', 'PositionMessage'),
 179        [Parameter(Mandatory = $false, Position = 2)]
 180        [switch]$GetErrorRecord = $true,
 181        [Parameter(Mandatory = $false, Position = 3)]
 182        [switch]$GetErrorInvocation = $true,
 183        [Parameter(Mandatory = $false, Position = 4)]
 184        [switch]$GetErrorException = $true,
 185        [Parameter(Mandatory = $false, Position = 5)]
 186        [switch]$GetErrorInnerException = $true
 187    )
 188
 189    Begin {
 190        ## If function was called without specifying an error record, then choose the latest error that occurred
 191        If (-not $ErrorRecord) {
 192            If ($global:Error.Count -eq 0) {
 193                #Write-Warning -Message "The `$Error collection is empty"
 194                Return
 195            }
 196            Else {
 197                [array]$ErrorRecord = $global:Error[0]
 198            }
 199        }
 200
 201        ## Allows selecting and filtering the properties on the error object if they exist
 202        [scriptblock]$SelectProperty = {
 203            Param (
 204                [Parameter(Mandatory = $true)]
 205                [ValidateNotNullorEmpty()]
 206                $InputObject,
 207                [Parameter(Mandatory = $true)]
 208                [ValidateNotNullorEmpty()]
 209                [string[]]$Property
 210            )
 211
 212            [string[]]$ObjectProperty = $InputObject | Get-Member -MemberType '*Property' | Select-Object -ExpandProperty 'Name'
 213            ForEach ($Prop in $Property) {
 214                If ($Prop -eq '*') {
 215                    [string[]]$PropertySelection = $ObjectProperty
 216                    Break
 217                }
 218                ElseIf ($ObjectProperty -contains $Prop) {
 219                    [string[]]$PropertySelection += $Prop
 220                }
 221            }
 222            Write-Output -InputObject $PropertySelection
 223        }
 224
 225        #  Initialize variables to avoid error if 'Set-StrictMode' is set
 226        $LogErrorRecordMsg = $null
 227        $LogErrorInvocationMsg = $null
 228        $LogErrorExceptionMsg = $null
 229        $LogErrorMessageTmp = $null
 230        $LogInnerMessage = $null
 231    }
 232    Process {
 233        If (-not $ErrorRecord) { Return }
 234        ForEach ($ErrRecord in $ErrorRecord) {
 235            ## Capture Error Record
 236            If ($GetErrorRecord) {
 237                [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord -Property $Property
 238                $LogErrorRecordMsg = $ErrRecord | Select-Object -Property $SelectedProperties
 239            }
 240
 241            ## Error Invocation Information
 242            If ($GetErrorInvocation) {
 243                If ($ErrRecord.InvocationInfo) {
 244                    [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord.InvocationInfo -Property $Property
 245                    $LogErrorInvocationMsg = $ErrRecord.InvocationInfo | Select-Object -Property $SelectedProperties
 246                }
 247            }
 248
 249            ## Capture Error Exception
 250            If ($GetErrorException) {
 251                If ($ErrRecord.Exception) {
 252                    [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord.Exception -Property $Property
 253                    $LogErrorExceptionMsg = $ErrRecord.Exception | Select-Object -Property $SelectedProperties
 254                }
 255            }
 256
 257            ## Display properties in the correct order
 258            If ($Property -eq '*') {
 259                #  If all properties were chosen for display, then arrange them in the order the error object displays them by default.
 260                If ($LogErrorRecordMsg) { [array]$LogErrorMessageTmp += $LogErrorRecordMsg }
 261                If ($LogErrorInvocationMsg) { [array]$LogErrorMessageTmp += $LogErrorInvocationMsg }
 262                If ($LogErrorExceptionMsg) { [array]$LogErrorMessageTmp += $LogErrorExceptionMsg }
 263            }
 264            Else {
 265                #  Display selected properties in our custom order
 266                If ($LogErrorExceptionMsg) { [array]$LogErrorMessageTmp += $LogErrorExceptionMsg }
 267                If ($LogErrorRecordMsg) { [array]$LogErrorMessageTmp += $LogErrorRecordMsg }
 268                If ($LogErrorInvocationMsg) { [array]$LogErrorMessageTmp += $LogErrorInvocationMsg }
 269            }
 270
 271            If ($LogErrorMessageTmp) {
 272                $LogErrorMessage = 'Error Record:'
 273                $LogErrorMessage += "`n-------------"
 274                $LogErrorMsg = $LogErrorMessageTmp | Format-List | Out-String
 275                $LogErrorMessage += $LogErrorMsg
 276            }
 277
 278            ## Capture Error Inner Exception(s)
 279            If ($GetErrorInnerException) {
 280                If ($ErrRecord.Exception -and $ErrRecord.Exception.InnerException) {
 281                    $LogInnerMessage = 'Error Inner Exception(s):'
 282                    $LogInnerMessage += "`n-------------------------"
 283
 284                    $ErrorInnerException = $ErrRecord.Exception.InnerException
 285                    $Count = 0
 286
 287                    While ($ErrorInnerException) {
 288                        [string]$InnerExceptionSeperator = '~' * 40
 289
 290                        [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrorInnerException -Property $Property
 291                        $LogErrorInnerExceptionMsg = $ErrorInnerException | Select-Object -Property $SelectedProperties | Format-List | Out-String
 292
 293                        If ($Count -gt 0) { $LogInnerMessage += $InnerExceptionSeperator }
 294                        $LogInnerMessage += $LogErrorInnerExceptionMsg
 295
 296                        $Count++
 297                        $ErrorInnerException = $ErrorInnerException.InnerException
 298                    }
 299                }
 300            }
 301
 302            If ($LogErrorMessage) { $Output = $LogErrorMessage }
 303            If ($LogInnerMessage) { $Output += $LogInnerMessage }
 304
 305            Write-Output -InputObject $Output
 306
 307            If (Test-Path -LiteralPath 'variable:Output') { Clear-Variable -Name 'Output' }
 308            If (Test-Path -LiteralPath 'variable:LogErrorMessage') { Clear-Variable -Name 'LogErrorMessage' }
 309            If (Test-Path -LiteralPath 'variable:LogInnerMessage') { Clear-Variable -Name 'LogInnerMessage' }
 310            If (Test-Path -LiteralPath 'variable:LogErrorMessageTmp') { Clear-Variable -Name 'LogErrorMessageTmp' }
 311        }
 312    }
 313    End {
 314    }
 315}
 316#endregion
 317
 318#region Function Write-Log
 319Function Write-Log {
 320<#
 321.SYNOPSIS
 322    Write messages to a log file in CMTrace.exe compatible format or Legacy text file format.
 323.DESCRIPTION
 324    Write messages to a log file in CMTrace.exe compatible format or Legacy text file format and optionally display in the console.
 325.PARAMETER Message
 326    The message to write to the log file or output to the console.
 327.PARAMETER Severity
 328    Defines message type. When writing to console or CMTrace.exe log format, it allows highlighting of message type.
 329    Options: 1 = Information (default), 2 = Warning (highlighted in yellow), 3 = Error (highlighted in red)
 330.PARAMETER Source
 331    The source of the message being logged. Also used as the event log source.
 332.PARAMETER ScriptSection
 333    The heading for the portion of the script that is being executed. Default is: $script:scriptSection.
 334.PARAMETER LogType
 335    Choose whether to write a CMTrace.exe compatible log file or a Legacy text log file.
 336.PARAMETER LoggingOptions
 337    Choose where to log 'Console', 'File', 'EventLog' or 'None'. You can choose multiple options.
 338.PARAMETER LogFileDirectory
 339    Set the directory where the log file will be saved.
 340.PARAMETER LogFileName
 341    Set the name of the log file.
 342.PARAMETER MaxLogFileSizeMB
 343    Maximum file size limit for log file in megabytes (MB). Default is 10 MB.
 344.PARAMETER LogName
 345    Set the name of the event log.
 346.PARAMETER EventID
 347    Set the event id for the event log entry.
 348.PARAMETER WriteHost
 349    Write the log message to the console.
 350.PARAMETER ContinueOnError
 351    Suppress writing log message to console on failure to write message to log file. Default is: $true.
 352.PARAMETER PassThru
 353    Return the message that was passed to the function
 354.PARAMETER VerboseMessage
 355    Specifies that the message is a debug message. Verbose messages only get logged if -LogDebugMessage is set to $true.
 356.PARAMETER DebugMessage
 357    Specifies that the message is a debug message. Debug messages only get logged if -LogDebugMessage is set to $true.
 358.PARAMETER LogDebugMessage
 359    Debug messages only get logged if this parameter is set to $true in the config XML file.
 360.EXAMPLE
 361    Write-Log -Message "Installing patch MS15-031" -Source 'Add-Patch' -LogType 'CMTrace'
 362.EXAMPLE
 363    Write-Log -Message "Script is running on Windows 8" -Source 'Test-ValidOS' -LogType 'Legacy'
 364.NOTES
 365    Slightly modified version of the PSADT logging cmdlet. I did not write the original cmdlet, please do not credit me for it.
 366.LINK
 367    https://psappdeploytoolkit.com
 368#>
 369    [CmdletBinding()]
 370    Param (
 371        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
 372        [AllowEmptyCollection()]
 373        [Alias('Text')]
 374        [string[]]$Message,
 375        [Parameter(Mandatory = $false, Position = 1)]
 376        [ValidateRange(1, 3)]
 377        [int16]$Severity = 1,
 378        [Parameter(Mandatory = $false, Position = 2)]
 379        [ValidateNotNullorEmpty()]
 380        [string]$Source = $script:LogSource,
 381        [Parameter(Mandatory = $false, Position = 3)]
 382        [ValidateNotNullorEmpty()]
 383        [string]$ScriptSection = $script:ScriptSection,
 384        [Parameter(Mandatory = $false, Position = 4)]
 385        [ValidateSet('CMTrace', 'Legacy')]
 386        [string]$LogType = 'CMTrace',
 387        [Parameter(Mandatory = $false, Position = 5)]
 388        [ValidateSet('Host', 'File', 'EventLog', 'None')]
 389        [string[]]$LoggingOptions = $script:LoggingOptions,
 390        [Parameter(Mandatory = $false, Position = 6)]
 391        [ValidateNotNullorEmpty()]
 392        [string]$LogFileDirectory = $script:LogFileDirectory,
 393        [Parameter(Mandatory = $false, Position = 7)]
 394        [ValidateNotNullorEmpty()]
 395        [string]$LogFileName = $($script:LogSource + '.log'),
 396        [Parameter(Mandatory = $false, Position = 8)]
 397        [ValidateNotNullorEmpty()]
 398        [int]$MaxLogFileSizeMB = 5,
 399        [Parameter(Mandatory = $false, Position = 9)]
 400        [ValidateNotNullorEmpty()]
 401        [string]$LogName = $script:LogName,
 402        [Parameter(Mandatory = $false, Position = 10)]
 403        [ValidateNotNullorEmpty()]
 404        [int32]$EventID = 1,
 405        [Parameter(Mandatory = $false, Position = 11)]
 406        [ValidateNotNullorEmpty()]
 407        [boolean]$ContinueOnError = $false,
 408        [Parameter(Mandatory = $false, Position = 12)]
 409        [switch]$PassThru = $false,
 410        [Parameter(Mandatory = $false, Position = 13)]
 411        [switch]$VerboseMessage = $false,
 412        [Parameter(Mandatory = $false, Position = 14)]
 413        [switch]$DebugMessage = $false,
 414        [Parameter(Mandatory = $false, Position = 15)]
 415        [boolean]$LogDebugMessage = $script:LogDebugMessages
 416    )
 417
 418    Begin {
 419        ## Get the name of this function
 420        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
 421
 422        ## Logging Variables
 423        #  Log file date/time
 424        [string]$LogTime = (Get-Date -Format 'HH:mm:ss.fff').ToString()
 425        [string]$LogDate = (Get-Date -Format 'MM-dd-yyyy').ToString()
 426        If (-not (Test-Path -LiteralPath 'variable:LogTimeZoneBias')) { [int32]$script:LogTimeZoneBias = [timezone]::CurrentTimeZone.GetUtcOffset([datetime]::Now).TotalMinutes }
 427        [string]$LogTimePlusBias = $LogTime + '-' + $script:LogTimeZoneBias
 428        #  Initialize variables
 429        [boolean]$WriteHost = $false
 430        [boolean]$WriteFile = $false
 431        [boolean]$WriteEvent = $false
 432        [boolean]$DisableLogging = $false
 433        [boolean]$ExitLoggingFunction = $false
 434        If ('Host' -in $LoggingOptions -and -not ($VerboseMessage -or $DebugMessage)) { $WriteHost = $true }
 435        If ('File' -in $LoggingOptions) { $WriteFile = $true }
 436        If ('EventLog' -in $LoggingOptions) { $WriteEvent = $true }
 437        If ('None' -in $LoggingOptions) { $DisableLogging = $true }
 438        #  Check if the script section is defined
 439        [boolean]$ScriptSectionDefined = $(-not [string]::IsNullOrEmpty($ScriptSection))
 440        #  Check if the source is defined
 441        [boolean]$SourceDefined = $(-not [string]::IsNullOrEmpty($Source))
 442        #  Check if the log name is defined
 443        [boolean]$LogNameDefined = $(-not [string]::IsNullOrEmpty($LogName))
 444        #  Check for overlapping log names if the log name does not exist
 445        If ($SourceDefined -and $LogNameDefined) {
 446        #  Check if the event log and event source exist
 447        [boolean]$LogNameNotExists = (-not [System.Diagnostics.EventLog]::Exists($LogName))
 448        [boolean]$LogSourceNotExists = (-not [System.Diagnostics.EventLog]::SourceExists($Source))
 449        #  Check for overlapping log names. The first 8 characters of the log name must be unique.
 450            If ($LogNameNotExists) {
 451                [string[]]$OverLappingLogName = Get-EventLog -List | Where-Object -Property 'Log' -Like  $($LogName.Substring(0,8) + '*') | Select-Object -ExpandProperty 'Log'
 452                If (-not [string]::IsNullOrEmpty($OverLappingLogName)) {
 453                    Write-Warning -Message "Overlapping log names:`n$($OverLappingLogName | Out-String)"
 454                    Write-Warning -Message 'Change the name of your log or use Remove-EventLog to remove the log(s) above!'
 455                }
 456            }
 457        }
 458        Else { Write-Warning -Message 'No Source '$Source' or Log Name '$LogName' defined. Skipping event log logging...' }
 459
 460        ## Create script block for generating CMTrace.exe compatible log entry
 461        [scriptblock]$CMTraceLogString = {
 462            Param (
 463                [string]$lMessage,
 464                [string]$lSource,
 465                [int16]$lSeverity
 466            )
 467            "<![LOG[$lMessage]LOG]!>" + "<time=`"$LogTimePlusBias`" " + "date=`"$LogDate`" " + "component=`"$lSource`" " + "context=`"$([Security.Principal.WindowsIdentity]::GetCurrent().Name)`" " + "type=`"$lSeverity`" " + "thread=`"$PID`" " + "file=`"$Source`">"
 468        }
 469
 470        ## Create script block for writing log entry to the console
 471        [scriptblock]$WriteLogLineToHost = {
 472            Param (
 473                [string]$lTextLogLine,
 474                [int16]$lSeverity
 475            )
 476            If ($WriteHost) {
 477                #  Only output using color options if running in a host which supports colors.
 478                If ($Host.UI.RawUI.ForegroundColor) {
 479                    Switch ($lSeverity) {
 480                        3 { Write-Host -Object $lTextLogLine -ForegroundColor 'Red' -BackgroundColor 'Black' }
 481                        2 { Write-Host -Object $lTextLogLine -ForegroundColor 'Yellow' -BackgroundColor 'Black' }
 482                        1 { Write-Host -Object $lTextLogLine }
 483                    }
 484                }
 485                #  If executing "powershell.exe -File <filename>.ps1 > log.txt", then all the Write-Host calls are converted to Write-Output calls so that they are included in the text log.
 486                Else {
 487                    Write-Output -InputObject $lTextLogLine
 488                }
 489            }
 490        }
 491
 492        ## Create script block for writing log entry to the console as verbose or debug message
 493        [scriptblock]$WriteLogLineToHostAdvanced = {
 494            Param (
 495                [string]$lTextLogLine
 496            )
 497            #  Only output using color options if running in a host which supports colors.
 498            If ($Host.UI.RawUI.ForegroundColor) {
 499                If ($VerboseMessage) {
 500                    Write-Verbose -Message $lTextLogLine
 501                }
 502                Else {
 503                    Write-Debug -Message $lTextLogLine
 504                }
 505            }
 506            #  If executing "powershell.exe -File <filename>.ps1 > log.txt", then all the Write-Host calls are converted to Write-Output calls so that they are included in the text log.
 507            Else {
 508                Write-Output -InputObject $lTextLogLine
 509            }
 510        }
 511
 512        ## Create script block for event writing log entry
 513        [scriptblock]$WriteToEventLog = {
 514            If ($WriteEvent -and $SourceDefined -and $LogNameDefined) {
 515                $EventType = Switch ($Severity) {
 516                    3 { 'Error' }
 517                    2 { 'Warning' }
 518                    1 { 'Information' }
 519                }
 520
 521                If ($LogNameNotExists -and (-not $LogSourceNotExists)) {
 522                    Try {
 523                        #  Delete event source if the log does not exist
 524                        $null = [System.Diagnostics.EventLog]::DeleteEventSource($Source)
 525                        $LogSourceNotExists = $true
 526                    }
 527                    Catch {
 528                        [boolean]$ExitLoggingFunction = $true
 529                        #  If error deleting event source, write message to console
 530                        If (-not $ContinueOnError) {
 531                            Write-Host -Object "[$LogDate $LogTime] [${CmdletName}] $ScriptSection :: Failed to create the event log source [$Source]. `n$(Resolve-Error)" -ForegroundColor 'Red'
 532                        }
 533                    }
 534                }
 535                If ($LogNameNotExists -or $LogSourceNotExists) {
 536                    Try {
 537                        #  Create event log
 538                        $null = New-EventLog -LogName $LogName -Source $Source -ErrorAction 'Stop'
 539                    }
 540                    Catch {
 541                        [boolean]$ExitLoggingFunction = $true
 542                        #  If error creating event log, write message to console
 543                        If (-not $ContinueOnError) {
 544                            Write-Host -Object "[$LogDate $LogTime] [${CmdletName}] $ScriptSection :: Failed to create the event log [$LogName`:$Source]. `n$(Resolve-Error)" -ForegroundColor 'Red'
 545                        }
 546                    }
 547                }
 548                Try {
 549                    #  Write to event log
 550                    Write-EventLog -LogName $LogName -Source $Source -EventId $EventID -EntryType $EventType -Category '0' -Message $EventLogLine -ErrorAction 'Stop'
 551                }
 552                Catch {
 553                    [boolean]$ExitLoggingFunction = $true
 554                    #  If error creating directory, write message to console
 555                    If (-not $ContinueOnError) {
 556                        Write-Host -Object "[$LogDate $LogTime] [${CmdletName}] $ScriptSection :: Failed to write to event log [$LogName`:$Source]. `n$(Resolve-Error)" -ForegroundColor 'Red'
 557                    }
 558                }
 559            }
 560        }
 561
 562        ## Exit function if it is a debug message and logging debug messages is not enabled in the config XML file
 563        If (($DebugMessage -or $VerboseMessage) -and (-not $LogDebugMessage)) { [boolean]$ExitLoggingFunction = $true; Return }
 564        ## Exit function if logging to file is disabled and logging to console host is disabled
 565        If (($DisableLogging) -and (-not $WriteHost)) { [boolean]$ExitLoggingFunction = $true; Return }
 566        ## Exit Begin block if logging is disabled
 567        If ($DisableLogging) { Return }
 568
 569        ## Create the directory where the log file will be saved
 570        If (-not (Test-Path -LiteralPath $LogFileDirectory -PathType 'Container')) {
 571            Try {
 572                $null = New-Item -Path $LogFileDirectory -Type 'Directory' -Force -ErrorAction 'Stop' -WhatIf:$false
 573            }
 574            Catch {
 575                [boolean]$ExitLoggingFunction = $true
 576                #  If error creating directory, write message to console
 577                If (-not $ContinueOnError) {
 578                    Write-Host -Object "[$LogDate $LogTime] [${CmdletName}] $ScriptSection :: Failed to create the log directory [$LogFileDirectory]. `n$(Resolve-Error)" -ForegroundColor 'Red'
 579                }
 580                Return
 581            }
 582        }
 583
 584        ## Assemble the fully qualified path to the log file
 585        [string]$LogFilePath = Join-Path -Path $LogFileDirectory -ChildPath $LogFileName
 586    }
 587    Process {
 588
 589        ForEach ($Msg in $Message) {
 590            ## If the message is not $null or empty, create the log entry for the different logging methods
 591            [string]$CMTraceMsg = ''
 592            [string]$ConsoleLogLine = ''
 593            [string]$EventLogLine = ''
 594            [string]$LegacyTextLogLine = ''
 595            If ($Msg) {
 596                #  Create the CMTrace log message
 597                If ($ScriptSectionDefined) { [string]$CMTraceMsg = "[$ScriptSection] :: $Msg" }
 598
 599                #  Create a Console and Legacy "text" log entry
 600                [string]$LegacyMsg = "[$LogDate $LogTime]"
 601                If ($ScriptSectionDefined) { [string]$LegacyMsg += " [$ScriptSection]" }
 602                If ($Source) {
 603                    [string]$EventLogLine = $Msg
 604                    [string]$ConsoleLogLine = "$LegacyMsg [$Source] :: $Msg"
 605                    Switch ($Severity) {
 606                        3 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Error] :: $Msg" }
 607                        2 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Warning] :: $Msg" }
 608                        1 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Info] :: $Msg" }
 609                    }
 610                }
 611                Else {
 612                    [string]$ConsoleLogLine = "$LegacyMsg :: $Msg"
 613                    [string]$EventLogLine = $Msg
 614                    Switch ($Severity) {
 615                        3 { [string]$LegacyTextLogLine = "$LegacyMsg [Error] :: $Msg" }
 616                        2 { [string]$LegacyTextLogLine = "$LegacyMsg [Warning] :: $Msg" }
 617                        1 { [string]$LegacyTextLogLine = "$LegacyMsg [Info] :: $Msg" }
 618                    }
 619                }
 620            }
 621
 622            ## Execute script block to write the log entry to the console as verbose or debug message
 623            & $WriteLogLineToHostAdvanced -lTextLogLine $ConsoleLogLine -lSeverity $Severity
 624
 625            ## Exit function if logging is disabled
 626            If ($ExitLoggingFunction) { Return }
 627
 628            ## Execute script block to create the CMTrace.exe compatible log entry
 629            [string]$CMTraceLogLine = & $CMTraceLogString -lMessage $CMTraceMsg -lSource $Source -lSeverity $lSeverity
 630
 631            ## Choose which log type to write to file
 632            If ($LogType -ieq 'CMTrace') {
 633                [string]$LogLine = $CMTraceLogLine
 634            }
 635            Else {
 636                [string]$LogLine = $LegacyTextLogLine
 637            }
 638
 639            ## Write the log entry to the log file and event log if logging is not currently disabled
 640            If ((-not $ExitLoggingFunction) -and (-not $DisableLogging)) {
 641                If ($WriteFile) {
 642                    ## Write to file log
 643                    Try {
 644                        $LogLine | Out-File -FilePath $LogFilePath -Append -NoClobber -Force -Encoding 'UTF8' -ErrorAction 'Stop' -WhatIf:$false
 645                    }
 646                    Catch {
 647                        If (-not $ContinueOnError) {
 648                            Write-Host -Object "[$LogDate $LogTime] [$ScriptSection] [${CmdletName}] :: Failed to write message [$Msg] to the log file [$LogFilePath]. `n$(Resolve-Error)" -ForegroundColor 'Red'
 649                        }
 650                    }
 651                }
 652                If ($WriteEvent) {
 653                    ## Write to event log
 654                    Try {
 655                        & $WriteToEventLog -lMessage $ConsoleLogLine -lName $LogName -lSource $Source -lSeverity $Severity
 656                    }
 657                    Catch {
 658                        If (-not $ContinueOnError) {
 659                            Write-Host -Object "[$LogDate $LogTime] [$ScriptSection] [${CmdletName}] :: Failed to write message [$Msg] to the log file [$LogFilePath]. `n$(Resolve-Error)" -ForegroundColor 'Red'
 660                        }
 661                    }
 662                }
 663            }
 664
 665            ## Execute script block to write the log entry to the console if $WriteHost is $true and $LogLogDebugMessage is not $true
 666            & $WriteLogLineToHost -lTextLogLine $ConsoleLogLine -lSeverity $Severity
 667        }
 668    }
 669    End {
 670        ## Archive log file if size is greater than $MaxLogFileSizeMB and $MaxLogFileSizeMB > 0
 671        Try {
 672            If ((-not $ExitLoggingFunction) -and (-not $DisableLogging)) {
 673                [IO.FileInfo]$LogFile = Get-ChildItem -LiteralPath $LogFilePath -ErrorAction 'Stop'
 674                [decimal]$LogFileSizeMB = $LogFile.Length / 1MB
 675                If (($LogFileSizeMB -gt $MaxLogFileSizeMB) -and ($MaxLogFileSizeMB -gt 0)) {
 676                    ## Change the file extension to "lo_"
 677                    [string]$ArchivedOutLogFile = [IO.Path]::ChangeExtension($LogFilePath, 'lo_')
 678                    [hashtable]$ArchiveLogParams = @{ ScriptSection = ${CmdletName}; Source = $Source; Severity = 2; LogFileDirectory = $LogFileDirectory; LogFileName = $LogFileName; LogType = $LogType; MaxLogFileSizeMB = 0; ContinueOnError = $ContinueOnError; PassThru = $false }
 679
 680                    ## Log message about archiving the log file
 681                    $ArchiveLogMessage = "Maximum log file size [$MaxLogFileSizeMB MB] reached. Rename log file to [$ArchivedOutLogFile]."
 682                    Write-Log -Message $ArchiveLogMessage @ArchiveLogParams
 683
 684                    ## Archive existing log file from <filename>.log to <filename>.lo_. Overwrites any existing <filename>.lo_ file. This is the same method SCCM uses for log files.
 685                    Move-Item -LiteralPath $LogFilePath -Destination $ArchivedOutLogFile -Force -ErrorAction 'Stop' -WhatIf:$false
 686
 687                    ## Start new log file and Log message about archiving the old log file
 688                    $NewLogMessage = "Previous log file was renamed to [$ArchivedOutLogFile] because maximum log file size of [$MaxLogFileSizeMB MB] was reached."
 689                    Write-Log -Message $NewLogMessage @ArchiveLogParams
 690                }
 691            }
 692        }
 693        Catch {
 694            ## If renaming of file fails, script will continue writing to log file even if size goes over the max file size
 695        }
 696        Finally {
 697            If ($PassThru) { Write-Output -InputObject $Message }
 698        }
 699    }
 700}
 701#endregion
 702
 703#region Function Show-Progress
 704Function Show-Progress {
 705<#
 706.SYNOPSIS
 707    Displays progress info.
 708.DESCRIPTION
 709    Displays progress info and maximizes code reuse by automatically calculating the progress steps.
 710.PARAMETER Actity
 711    Specifies the progress activity. Default: 'Cleaning Up Configuration Manager Client Cache, Please Wait...'.
 712.PARAMETER Status
 713    Specifies the progress status.
 714.PARAMETER CurrentOperation
 715    Specifies the current operation.
 716.PARAMETER Step
 717    Specifies the progress step. Default: $script:Step ++.
 718.PARAMETER Steps
 719    Specifies the progress steps. Default: $script:Steps ++.
 720.PARAMETER ID
 721    Specifies the progress bar id.
 722.PARAMETER Delay
 723    Specifies the progress delay in milliseconds. Default: 0.
 724.PARAMETER Loop
 725    Specifies if the call comes from a loop.
 726.EXAMPLE
 727    Show-Progress -Activity 'Cleaning Up Configuration Manager Client Cache, Please Wait...' -Status 'Cleaning WMI' -Step ($Step++) -Delay 200
 728.INPUTS
 729    None.
 730.OUTPUTS
 731    None.
 732.NOTES
 733    Created by Ioan Popovici.
 734    v2.0.0 - 2021-01-01
 735
 736    This is an private function should typically not be called directly.
 737    Credit to Adam Bertram.
 738
 739    ## !! IMPORTANT !! ##
 740    #  You need to tokenize the scripts steps at the beginning of the script in order for Show-Progress to work:
 741
 742    ## Get script path and name
 743    [string]$ScriptPath = [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition)
 744    [string]$ScriptName = [System.IO.Path]::GetFileName($MyInvocation.MyCommand.Definition)
 745    [string]$ScriptFullName = Join-Path -Path $ScriptPath -ChildPath $ScriptName
 746    #  Get progress steps
 747    $ProgressSteps = $(([System.Management.Automation.PsParser]::Tokenize($(Get-Content -Path $ScriptFullName), [ref]$null) | Where-Object { $PSItem.Type -eq 'Command' -and $PSItem.Content -eq 'Show-Progress' }).Count)
 748    $ForEachSteps = $(([System.Management.Automation.PsParser]::Tokenize($(Get-Content -Path $ScriptFullName), [ref]$null) | Where-Object { $PSItem.Type -eq 'Keyword' -and $PSItem.Content -eq 'ForEach' }).Count)
 749    #  Set progress steps
 750    $script:Steps = $ProgressSteps - $ForEachSteps
 751    $script:Step = 0
 752.LINK
 753    https://adamtheautomator.com/building-progress-bar-powershell-scripts/
 754.LINK
 755    https://MEM.Zone
 756.LINK
 757    https://MEM.Zone/GIT
 758.LINK
 759    https://MEM.Zone/ISSUES
 760.COMPONENT
 761    Powershell
 762.FUNCTIONALITY
 763    Show Progress
 764#>
 765    [CmdletBinding()]
 766    Param (
 767        [Parameter(Mandatory=$false,Position=0)]
 768        [ValidateNotNullorEmpty()]
 769        [Alias('act')]
 770        [string]$Activity = 'Cleaning Up Configuration Manager Client Cache, Please Wait...',
 771        [Parameter(Mandatory=$true,Position=1)]
 772        [ValidateNotNullorEmpty()]
 773        [Alias('sta')]
 774        [string]$Status,
 775        [Parameter(Mandatory=$false,Position=2)]
 776        [ValidateNotNullorEmpty()]
 777        [Alias('cro')]
 778        [string]$CurrentOperation,
 779        [Parameter(Mandatory=$false,Position=3)]
 780        [ValidateNotNullorEmpty()]
 781        [Alias('pid')]
 782        [int]$ID = 0,
 783        [Parameter(Mandatory=$false,Position=4)]
 784        [ValidateNotNullorEmpty()]
 785        [Alias('ste')]
 786        [int]$Step = $script:Step ++,
 787        [Parameter(Mandatory=$false,Position=5)]
 788        [ValidateNotNullorEmpty()]
 789        [Alias('sts')]
 790        [int]$Steps = $script:Steps,
 791        [Parameter(Mandatory=$false,Position=6)]
 792        [ValidateNotNullorEmpty()]
 793        [Alias('del')]
 794        [string]$Delay = 0,
 795        [Parameter(Mandatory=$false,Position=7)]
 796        [ValidateNotNullorEmpty()]
 797        [Alias('lp')]
 798        [switch]$Loop
 799    )
 800    Begin {
 801
 802
 803    }
 804    Process {
 805        Try {
 806            If ($Step -eq 0) {
 807                $Step ++
 808                $script:Step ++
 809                $Steps ++
 810                $script:Steps ++
 811            }
 812            If ($Steps -eq 0) {
 813                $Steps ++
 814                $script:Steps ++
 815            }
 816
 817            [boolean]$Completed = $false
 818            [int]$PercentComplete = $($($Step / $Steps) * 100)
 819
 820            If ($PercentComplete -ge 100)  {
 821                $PercentComplete = 100
 822                $Completed = $true
 823                $script:CurrentStep ++
 824                $script:Step = $script:CurrentStep
 825                $script:Steps = $script:DefaultSteps
 826            }
 827
 828            ## Debug information
 829            Write-Verbose -Message "Percent Step: $Step"
 830            Write-Verbose -Message "Percent Steps: $Steps"
 831            Write-Verbose -Message "Percent Complete: $PercentComplete"
 832            Write-Verbose -Message "Completed: $Completed"
 833
 834            ##  Show progress
 835            Write-Progress -Activity $Activity -Status $Status -CurrentOperation $CurrentOperation -ID $ID -PercentComplete $PercentComplete -Completed:$Completed
 836            If ($Delay -ne 0) { Start-Sleep -Milliseconds $Delay }
 837        }
 838        Catch {
 839            Throw (New-Object System.Exception("Could not Show progress status [$Status]! $($PSItem.Exception.Message)", $PSItem.Exception))
 840        }
 841    }
 842}
 843#endregion
 844
 845#region Function Get-MSGraphAPIAccessToken
 846Function Get-MSGraphAPIAccessToken {
 847<#
 848.SYNOPSIS
 849    Gets a Microsoft Graph API access token.
 850.DESCRIPTION
 851    Gets a Microsoft Graph API access token, by using an application registered in EntraID.
 852.PARAMETER TenantID
 853    Specifies the tenant ID.
 854.PARAMETER ClientID
 855    Specify the Application Client ID to use.
 856.PARAMETER Secret
 857    Specify the Application Client Secret to use.
 858.PARAMETER Scope
 859    Specify the scope to use.
 860    Default is: 'https://graph.microsoft.com/.default'.
 861.PARAMETER GrantType
 862    Specify the grant type to use.
 863    Default is: 'client_credentials'.
 864.EXAMPLE
 865    Get-MSGraphAPIAccessToken -TenantID $TenantID -ClientID $ClientID -Secret $Secret -Scope 'https://graph.microsoft.com/.default' -GrantType 'client_credentials'
 866.EXAMPLE
 867    Get-MSGraphAPIAccessToken -TenantID $TenantID -ClientID $ClientID -Secret $Secret
 868.INPUTS
 869    None.
 870.OUTPUTS
 871    System.String
 872.NOTES
 873    Created by Ioan Popovici
 874    v1.0.0 - 2024-01-11
 875.LINK
 876    https://MEMZ.one/Invoke-MSGraphAPI
 877.LINK
 878    https://MEMZ.one/Invoke-MSGraphAPI-CHANGELOG
 879.LINK
 880    https://MEMZ.one/Invoke-MSGraphAPI-GIT
 881.LINK
 882    https://MEM.Zone/ISSUES
 883.COMPONENT
 884    MSGraph
 885.FUNCTIONALITY
 886    Gets a Microsoft Graph API Access Token.
 887#>
 888    [CmdletBinding()]
 889    Param (
 890        [Parameter(Mandatory = $true, HelpMessage = 'Specify the tenant ID.', Position = 0)]
 891        [ValidateNotNullorEmpty()]
 892        [Alias('Tenant')]
 893        [string]$TenantID,
 894        [Parameter(Mandatory = $true, HelpMessage = 'Specify the Application (Client) ID to use.', Position = 1)]
 895        [ValidateNotNullorEmpty()]
 896        [Alias('ApplicationClientID')]
 897        [string]$ClientID,
 898        [Parameter(Mandatory = $true, HelpMessage = 'Specify the Application (Client) Secret to use.', Position = 2)]
 899        [ValidateNotNullorEmpty()]
 900        [Alias('ApplicationClientSecret')]
 901        [string]$ClientSecret,
 902        [Parameter(Mandatory = $false, HelpMessage = 'Specify the scope to use.', Position = 3)]
 903        [ValidateNotNullorEmpty()]
 904        [Alias('GrantScope')]
 905        [string]$Scope = 'https://graph.microsoft.com/.default',
 906        [Parameter(Mandatory = $false, HelpMessage = 'Specify the grant type to use.', Position = 4)]
 907        [ValidateNotNullorEmpty()]
 908        [Alias('AccessType')]
 909        [string]$GrantType = 'client_credentials'
 910    )
 911
 912    Begin {
 913
 914        ## Get the name of this function and write verbose header
 915        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
 916
 917        ## Assemble the token body for the API call. You can store the secrets in Azure Key Vault and retrieve them from there.
 918        [hashtable]$Body = @{
 919            client_id     = $ClientID
 920            scope         = $Scope
 921            client_secret = $ClientSecret
 922            grant_type    = $GrantType
 923        }
 924
 925        ## Assembly the URI for the API call
 926        [string]$Uri = -join ('https://login.microsoftonline.com/', $TenantID, '/oauth2/v2.0/token')
 927
 928        ## Write Debug information
 929        Write-Debug -Message "Uri: $Uri"
 930        Write-Debug -Message "Body: $($Body | Out-String)"
 931    }
 932    Process {
 933        Try {
 934
 935            ## Get the access token
 936            $Response = Invoke-RestMethod -Method 'POST' -Uri $Uri -ContentType 'application/x-www-form-urlencoded' -Body $Body -UseBasicParsing
 937
 938            ## Assemble output object
 939            $Output = [pscustomobject]@{
 940                access_token = $Response.access_token
 941                expires_in   = $Response.expires_in
 942                granted_on   = $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
 943            }
 944        }
 945        Catch {
 946            [string]$Message = "Error getting MSGraph API Access Token for TenantID '{0}' with ClientID '{1}'.`n{2}" -f $TenantID, $ClientID, $(Resolve-Error)
 947            Write-Log -Message $Message -Severity 3 -ScriptSection ${CmdletName} -EventID 666
 948            Write-Error -Message $Message
 949        }
 950        Finally {
 951            Write-Output -InputObject $Output
 952        }
 953    }
 954    End {
 955    }
 956}
 957#endregion
 958
 959#region Function Invoke-MSGraphAPI
 960Function Invoke-MSGraphAPI {
 961<#
 962.SYNOPSIS
 963    Invokes the Microsoft Graph API.
 964.DESCRIPTION
 965    Invokes the Microsoft Graph API with paging support.
 966.PARAMETER Method
 967    Specify the method to use.
 968    Available options are 'GET', 'POST', 'PATCH', 'PUT' and 'DELETE'.
 969    Default is: 'GET'.
 970.PARAMETER Token
 971    Specify the access token to use.
 972.PARAMETER Version
 973    Specify the version of the Microsoft Graph API to use.
 974    Available options are 'Beta' and 'v1.0'.
 975    Default is: 'Beta'.
 976.PARAMETER Resource
 977    Specify the resource to query.
 978    Default is: 'deviceManagement/managedDevices'.
 979.PARAMETER Parameter
 980    Specify the parameter to use. Make sure to use the correct syntax and escape special characters with a backtick.
 981    Default is: $null.
 982.PARAMETER Body
 983    Specify the request body to use.
 984    Default is: $null.
 985.PARAMETER ContentType
 986    Specify the content type to use.
 987    Default is: 'application/json'.
 988.EXAMPLE
 989    Invoke-MSGraphAPI -Method 'GET' -Token $Token -Version 'Beta' -Resource 'deviceManagement/managedDevices' -Parameter "filter=operatingSystem like 'Windows' and deviceName like 'MEM-Zone-PC'"
 990.EXAMPLE
 991    Invoke-MSGraphAPI -Token $Token -Resource 'users'
 992.INPUTS
 993    None.
 994.OUTPUTS
 995    System.Object
 996.NOTES
 997    Created by Ioan Popovici
 998    v1.0.0 - 2024-01-11
 999.LINK
1000    https://MEMZ.one/Invoke-MSGraphAPI
1001.LINK
1002    https://MEMZ.one/Invoke-MSGraphAPI-CHANGELOG
1003.LINK
1004    https://MEMZ.one/Invoke-MSGraphAPI-GIT
1005.LINK
1006    https://MEM.Zone/ISSUES
1007.COMPONENT
1008    MSGraph
1009.FUNCTIONALITY
1010    Invokes the Microsoft Graph API.
1011#>
1012    [CmdletBinding()]
1013    Param (
1014        [Parameter(Mandatory = $false, HelpMessage = 'Specify the method to use.', Position = 0)]
1015        [ValidateSet('GET', 'POST', 'PATCH', 'PUT', 'DELETE')]
1016        [Alias('HTTPMethod')]
1017        [string]$Method = 'GET',
1018        [Parameter(Mandatory = $true, HelpMessage = 'Specify the access token to use.', Position = 1)]
1019        [ValidateNotNullorEmpty()]
1020        [Alias('AccessToken')]
1021        [string]$Token,
1022        [Parameter(Mandatory = $false, HelpMessage = 'Specify the version of the Microsoft Graph API to use.', Position = 2)]
1023        [ValidateSet('Beta', 'v1.0')]
1024        [Alias('GraphVersion')]
1025        [string]$Version = 'Beta',
1026        [Parameter(Mandatory = $true, HelpMessage = 'Specify the resource to query.', Position = 3)]
1027        [ValidateNotNullorEmpty()]
1028        [Alias('APIResource')]
1029        [string]$Resource,
1030        [Parameter(Mandatory = $false, HelpMessage = 'Specify the parameters to use.', Position = 4)]
1031        [ValidateNotNullorEmpty()]
1032        [Alias('QueryParameter')]
1033        [string]$Parameter,
1034        [Parameter(Mandatory = $false, HelpMessage = 'Specify the request body to use.', Position = 5)]
1035        [ValidateNotNullorEmpty()]
1036        [Alias('RequestBody')]
1037        [string]$Body,
1038        [Parameter(Mandatory = $false, HelpMessage = 'Specify the content type to use.', Position = 6)]
1039        [ValidateNotNullorEmpty()]
1040        [Alias('Type')]
1041        [string]$ContentType = 'application/json'
1042    )
1043
1044    Begin {
1045
1046        ## Get the name of this function and write verbose header
1047        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
1048
1049        ## Assemble the URI for the API call
1050        [string]$Uri = -join ("https://graph.microsoft.com/", $Version, "/", $Resource)
1051        If (-not [string]::IsNullOrWhiteSpace($Parameter)) { $Uri += "`?`$$Parameter" }
1052
1053        ## Assembly parameters for the API call
1054        [hashtable]$Parameters = @{
1055            'Uri'         = $Uri
1056            'Method'      = $Method
1057            'Headers'     = @{
1058                'Content-Type'  = 'application\json'
1059                'Authorization' = "Bearer $Token"
1060            }
1061            'ContentType' = $ContentType
1062        }
1063        If (-not [string]::IsNullOrWhiteSpace($Body)) { $Parameters.Add('Body', $Body) }
1064
1065        ## Write Debug information
1066        Write-Debug -Message "Uri: $Uri"
1067    }
1068    Process {
1069        Try {
1070
1071            ## Invoke the MSGraph API
1072            $Output = Invoke-RestMethod @Parameters
1073
1074            ## If there are more than 1000 rows, use paging. Only for GET method.
1075            If (-not [string]::IsNullOrEmpty($Output.'@odata.nextLink')) {
1076                #  Assign the nextLink to the Uri
1077                $Parameters.Uri = $Output.'@odata.nextLink'
1078                [array]$Output += Do {
1079                    #  Invoke the MSGraph API
1080                    $OutputPage = Invoke-RestMethod @Parameters
1081                    #  Assign the nextLink to the Uri
1082                    $Parameters.Uri = $OutputPage.'@odata.nextLink'
1083                    #  Write Debug information
1084                    Write-Debug -Message "Parameters:`n$($Parameters | Out-String)"
1085                    #  Return the OutputPage
1086                    $OutputPage
1087                }
1088                Until ([string]::IsNullOrEmpty($OutputPage.'@odata.nextLink'))
1089            }
1090            Write-Verbose -Message "Got '$($Output.Count)' Output pages."
1091        }
1092        Catch {
1093            [string]$Message = "Error invoking MSGraph API version '{0}' for resource '{1}' using '{2}' method.`n{3}" -f $Version, $Resource, $Method, $(Resolve-Error)
1094            Write-Log -Message $Message -Severity 3 -ScriptSection ${CmdletName} -EventID 666
1095            Write-Error -Message $Message
1096        }
1097        Finally {
1098            $Output = If ($Output.value) { $Output.value } Else { $Output }
1099            Write-Output -InputObject $Output
1100        }
1101    }
1102    End {
1103    }
1104}
1105#endregion
1106
1107#endregion
1108##*=============================================
1109##* END FUNCTION LISTINGS
1110##*=============================================
1111
1112##*=============================================
1113##* SCRIPT BODY
1114##*=============================================
1115#region ScriptBody
1116
1117Try {
1118
1119    ## Set the script section
1120    $script:ScriptSection = 'Main'
1121
1122    ## Write Start verbose message
1123    Write-Log -Message 'Start' -VerboseMessage
1124
1125    ## Get API Token
1126    $Token = (Get-MSGraphAPIAccessToken -TenantID $TenantID -ClientID $ClientID -ClientSecret $ClientSecret -ErrorAction 'Stop').access_token
1127
1128    ## Get the device information
1129    Write-Verbose -Message "Getting device information, this might take a while..." -Verbose
1130
1131    #  Assemble the Parameter filter value depending if the DeviceOS and DeviceName parameters are specified
1132    $Parameter = If ($DeviceOS -ne 'All') { "filter=startswith(operatingSystem, '$DeviceOS')" }
1133    $Parameter += If ($DeviceName -ne 'All') {
1134        If ($DeviceOS -eq 'All') { "filter=deviceName eq '$DeviceName'" } Else { " and deviceName eq '$DeviceName'" }
1135    }
1136
1137    #  Set the parameters for the API call and add the Parameter parameter as a filter if it is not empty
1138    $Parameters = @{
1139        Token = $Token
1140        Resource = 'deviceManagement/managedDevices'
1141    }
1142    If (-not [string]::IsNullOrWhiteSpace($Parameter)) { $Parameters.Add('Parameter', $Parameter) }
1143
1144    #  Get the device information from the MSGraph API
1145    $Devices = Invoke-MSGraphAPI @Parameters -ErrorAction 'Stop'
1146    Write-Verbose -Message "Retrieved $($Devices.Count) devices."
1147
1148    ## Process devices
1149    ForEach ($Device in $Devices) {
1150
1151        ## Set variables
1152        [int]$RenamedCounter = 0
1153        [string]$Output = ''
1154        [string]$SerialNumber = $Device.serialNumber
1155        [string]$UserPrincipalName = $Device.userPrincipalName
1156        [string]$DeviceName = $Device.deviceName
1157        [string]$DeviceID = $Device.id
1158        [string]$OperatingSystem = $Device.operatingSystem
1159        [int]$DeviceOwnerType = $Device.managedDeviceOwnerType
1160        [boolean]$isSupervised = $Device.isSupervised
1161        #  Initialize the prefix variable with the script parameter value
1162        [string]$Prefix = $PSBoundParameters['Prefix']
1163        #  Convert to CAPS, shorten to 6 characters, convert to upper case and clean Prefix by removing any non-alphanumeric characters
1164        If (-not [string]::IsNullOrWhiteSpace($Prefix)) { $Prefix = $($Prefix.Substring(0, [System.Math]::Min(6, $Prefix.Length))).ToUpper() -replace ('[\W | /_]', '') }
1165
1166        ## Show progress bar
1167        Show-Progress -Status "Processing Devices for Rename --> [$DeviceName]" -Steps $Devices.Count
1168
1169        ## Check for supported device operating system and corporate owned device
1170        If ($OperatingSystem -notin $SupportedOperatingSystems -or $DeviceOwnerType -ne 'company') {
1171            [string]$Message = "Device '$DeviceName' with operating system '$OperatingSystem' and ownership type '$DeviceOwnerType' is not supported. Skipping..."
1172            Write-Warning -Message $Message -Verbose
1173            Write-Log -Message $Message -Severity 3 -EventID 666
1174            #  Skip to next device in the loop
1175            Continue
1176        }
1177        ## Check for supervised iOS/iPadOS device
1178        If ($OperatingSystem -eq 'iOS/iPadOS' -and -not $isSupervised) {
1179            [string]$Message = "Device '$DeviceName' with operating system '$OperatingSystem' is not supervised. Skipping..."
1180            Write-Warning -Message $Message -Verbose
1181            Write-Log -Message $Message -Severity 3 -EventID 666
1182            #  Skip to next device in the loop
1183            Continue
1184        }
1185
1186        ## Get device assigned user attribute information and set the Prefix if specified
1187        If ($PSCmdlet.ParameterSetName -eq 'UserAttribute') {
1188            Try {
1189                $UserInfo = Invoke-MSGraphAPI -Token $Token -Resource 'users' -Parameter "filter=userPrincipalName eq '$UserPrincipalName'" -ErrorAction 'Stop'
1190                    #  Get the user attribute
1191                    [string]$UserAttribute = $UserInfo.$PrefixFromUserAttribute
1192                    #  Convert to CAPS, shorten to 6 characters, convert to upper case and clean UserAttribute by removing any non-alphanumeric characters
1193                    $UserAttribute = $($UserAttribute.Substring(0, [System.Math]::Min(6, $UserAttribute.Length))).ToUpper() -replace ('[\W | /_]', '')
1194                    #  Set the prefix if the user attribute is not empty
1195                    If (-not [string]::IsNullOrEmpty($UserAttribute)) { $Prefix = $UserAttribute } Else { Throw 'User attribute is empty!' }
1196            }
1197            Catch {
1198                [string]$Message = "Error getting user information for device '{0}' with owner '{1}', check if the device has a user assigned. Skipping...`n{2}" -f $DeviceName, $UserPrincipalName, $(Resolve-Error)
1199                Write-Warning -Message $Message -Verbose
1200                Write-Log -Message $Message -Severity 3 -EventID 666
1201                #  Skip to next device in the loop
1202                Continue
1203            }
1204        }
1205
1206        ## Check if the device has a serialnumber and that it's valid
1207        [boolean]$IsValidSerialNumber = If (-not [string]::IsNullOrEmpty($SerialNumber) -and ($SerialNumber -ne 'SystemSerialNumber')) { $true } Else { $false }
1208
1209        ## Clean serialnumber by removing any non-alphanumeric characters
1210        If (-not $IsValidSerialNumber) {
1211            [string]$Message = "Device '$DeviceName' does not have a valid serialnumber. Skipping..."
1212            Write-Warning -Message $Message -Verbose
1213            Write-Log -Message $Message -Severity 3 -EventID 666
1214            Continue
1215        }
1216
1217        ## Remove any non-alphanumeric characters from the serial number and convert to upper case
1218        $SerialNumber = ($SerialNumber -replace ('[\W | /_]', '')).ToUpper()
1219
1220        ## Trim serial number to 15 characters for windows devices
1221        If ($OperatingSystem -eq 'windows') {
1222            $NewDeviceName = -join ($Prefix,'-',$SerialNumber)
1223            $MaxSerialNumberLength = 15 - $Prefix.Length -1
1224            $SerialNumber = $SerialNumber.subString(0, [System.Math]::Min($MaxSerialNumberLength , $NewDeviceName.Length))
1225        }
1226
1227        ## Assemble the new device name
1228        $NewDeviceName = -join ($Prefix,'-',$SerialNumber)
1229
1230        ## Rename device if it has not been already renamed
1231        Try {
1232            If ($DeviceName -ne $NewDeviceName) {
1233                $Parameters = @{
1234                    Method = 'POST'
1235                    Token = $Token
1236                    Resource = "deviceManagement/managedDevices('$DeviceID')/setDeviceName"
1237                    Body = @{ deviceName = $NewDeviceName } | ConvertTo-Json
1238                    ContentType = 'application/json'
1239                    ErrorAction = 'Stop'
1240                }
1241                ##  Rename device with ShouldProcess support
1242                [boolean]$ShouldProcess = $PSCmdlet.ShouldProcess("$DeviceName", "Rename to $NewDeviceName")
1243                If ($ShouldProcess) { Invoke-MSGraphAPI @Parameters }
1244                #  If operation is successful, output the result
1245                $Output = "Device '{0}' renamed to '{1}'." -f $DeviceName, $NewDeviceName
1246                $RenamedCounter++
1247            }
1248            Else {
1249                $Output = "Device '{0}' is already named '{1}'." -f $DeviceName, $NewDeviceName
1250            }
1251        }
1252        Catch {
1253            Write-Log -Message "Error renaming device '$DeviceName' to '$NewDeviceName'.`n$(Resolve-Error)" -Severity 3 -EventID 666
1254            Continue
1255        }
1256        Finally {
1257            Write-Log -Message $Output
1258        }
1259    }
1260}
1261Catch {
1262    Write-Log -Message "Error renaming device.`n$(Resolve-Error)" -Severity 3 -EventID 666
1263}
1264Finally {
1265    Write-Log -Message "Successfully renamed '$RenamedCounter' devices." -EventID 2
1266    Write-Log -Message 'Stop' -VerboseMessage
1267}
1268
1269#endregion
1270##*=============================================
1271##* END SCRIPT BODY
1272##*=============================================

SHARE

article card image dark article card image light

Published by · Dec 8, 2023 intune · 5 mins read

Intune Logs: A Deep Dive into Locations, Interpretation, and Configuration

A Comprehensive Guide to Locations, Interpretation, and Configuration of Intune Logs ...

See More
article card image dark article card image light

Published by · Jun 17, 2023 tools · 1 mins read

Introducing: Windows Update Database Reinitialization Tool

Proactively repair corrupted Windows Update Database with Powershell and Configuration Manager ...

See More