SDM.psm1
<#
.SYNOPSIS PowerShell library module for automation (SDM: Stijn Denruyter Module). .DESCRIPTION This PowerShell module contains functions that can be used in other PowerShell scripts for automation. .NOTES FileName: SDM.psm1 Version: 2.1 Blog: https://blog.stijndenruyter.be Author: Stijn Denruyter Twitter: @StijnDenruyter Created: 02-11-2020 Updated: 07-06-2022 Version history: 1.0 - (02-11-2020) First version. 2.0 - (17-02-2022) Add comment-based help keywords. 2.1 - (07-06-2022) Updated Test-SDMComputerConnection - Add WinRM connectivity check. .LINK https://www.powershellgallery.com #> #region Declaration variables $Global:LogFilePath = "" #Used to write the log entries in this module to a custom log file. #This variable must be initialized in the script that calls this module, otherwise no log file will be used. $Global:LogFileSize = 10 #The maximum size of a log file before it gets archived. Default 10MB. #This can be overwritten in the script that calls this module. $Global:LogFileArchiveNumber = 10 #The maximum number of archived log files. Default 10 log files. #This can be overwritten in the script that calls this module. $ErrorActionPreference = "Stop" #endregion Declaration variables #region Microsoft Endpoint Manager Configuration Manager Function Import-SDMMEMCMModule { <# .SYNOPSIS Imports the MEMCM PowerShell module that is locally installed. .DESCRIPTION Prerequisite: MEMCM PowerShell module installed on the system where this command is being used. #> Write-SDMLog -Message "Import MEMCM PowerShell module..." -Severity Info If (-not (Get-Module -Name "ConfigurationManager")) { If (Test-Path -Path "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1") { Try { Import-Module "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1" -Global If (Get-Module -Name "ConfigurationManager") { Write-SDMLog -Message "MEMCM PowerShell module imported" -Severity Info } Else { Throw } } Catch { Write-SDMLog -Message "Failed to import the MEMCM PowerShell module: $($_.Exception.Message)" -Severity Error Break } } ElseIf (Test-Path -Path "C:\Program Files (x86)\Microsoft Endpoint Manager\AdminConsole\bin\ConfigurationManager.psd1") { Try { Import-Module "C:\Program Files (x86)\Microsoft Endpoint Manager\AdminConsole\bin\ConfigurationManager.psd1" -Global If (Get-Module -Name "ConfigurationManager") { Write-SDMLog -Message "MEMCM PowerShell module imported" -Severity Info } Else { Throw } } Catch { Write-SDMLog -Message "Failed to import the MEMCM PowerShell module: $($_.Exception.Message)" -Severity Error Break } } Else { Write-SDMLog -Message "MEMCM PowerShell module is not installed on this system" -Severity Error Break } } Else { Write-SDMLog -Message "MEMCM PowerShell module already imported" -Severity Info } } Function Import-SDMMEMCMDevice { <# .SYNOPSIS Imports a device into MEMCM. .DESCRIPTION Imports a device based on MAC address to MEMCM and adds it to a device collection. .PARAMETER Name Specifies the name of the device. .PARAMETER MACAddress Specifies the MAC address of the device. .PARAMETER DeviceCollection Specifies the name of the device collection the device should be added to. .PARAMETER MEMCMServer Specifies the name of the MEMCM server. .PARAMETER MEMCMSiteCode Specifies the site code name of the MEMCM server. .EXAMPLE Import-SDMMEMCMDevice -Name "CLIENT01" -MACAddress "00:00:00:00:00:01" -MEMCMServer "SERVER01.domain.local" -MEMCMSiteCode "MCM" .EXAMPLE Import-SDMMEMCMDevice -Name "CLIENT01" -MACAddress "00:00:00:00:00:01" -DeviceCollection "All client devices" -MEMCMServer "SERVER01.domain.local" -MEMCMSiteCode "MCM" #> [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$Name, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$MACAddress, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [String]$DeviceCollection = "", [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$MEMCMServer, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$MEMCMSiteCode ) Write-SDMLog -Message "Import MEMCM device $($Name)..." -Severity Info Mount-SDMMEMCMSiteCode -MEMCMServer $MEMCMServer -MEMCMSiteCode $MEMCMSiteCode Try { If (-not ($MEMCMSiteCode[-1] -eq ":")) { $MEMCMSiteCodeWithSuffix = "$($MEMCMSiteCode):" } Else { $MEMCMSiteCodeWithSuffix = $MEMCMSiteCode $MEMCMSiteCode = $MEMCMSiteCode -Replace ".$" } Set-Location -Path $MEMCMSiteCodeWithSuffix } Catch { Write-SDMLog -Message "Failed to set the MEMCM site code to $($MEMCMSiteCode): $($_.Exception.Message)" -Severity Error Break } If (($DeviceCollection -ne "") -and (-not (Get-CMDeviceCollection -Name $DeviceCollection))) { Write-SDMLog -Message "Device collection $($DeviceCollection) does not exist" -Severity Error Break } If (Get-CMDevice -Name $Name) { Write-SDMLog -Message "MEMCM device $($Name) already exist. Remove MEMCM device $($Name)..." -Severity Info Try { Remove-CMDevice -DeviceName $Name -Force If (-not (Get-CMDevice -Name $Name)) { Write-SDMLog -Message "MEMCM device $($Name) removed" -Severity Info } Else { Throw } } Catch { Write-SDMLog -Message "Failed to remove MEMCM device $($Name): $($_.Exception.Message)" -Severity Error Break } } Try { If ($DeviceCollection -ne "") { Import-CMComputerInformation -ComputerName $Name -CollectionName $DeviceCollection -MacAddress $MacAddress } Else { Import-CMComputerInformation -ComputerName $Name -MacAddress $MacAddress } $TimeOut = 20 #Time-out after 10 minutes Do { $TimeOut = $TimeOut - 1 Start-Sleep -Seconds 30 } While (((Get-CMDevice -Name $Name) -eq $Null) -and ($TimeOut -ne 0)) If (Get-CMDevice -Name $Name) { Write-SDMLog -Message "MEMCM device $($Name) imported" -Severity Info } Else { Throw } } Catch { Write-SDMLog -Message "Failed to import MEMCM device $($Name): $($_.Exception.Message)" -Severity Error Break } If ($DeviceCollection -ne "") { $ExitLoop = $False $RetryCount = 5 Do { Try { $DeviceCollectionQuery = Get-WmiObject -Namespace "Root\SMS\Site_$($MEMCMSiteCode)" -Class SMS_Collection -ComputerName $MEMCMServer -Filter "Name='$DeviceCollection'" $DeviceCollectionQuery.RequestRefresh() | Out-Null $TimeOut = 180 #Time-out after 30 minutes Do { $TimeOut = $TimeOut - 1 Start-Sleep -Seconds 10 } While (((Get-CMDevice -CollectionName $DeviceCollection -Name $Name) -eq $Null) -and ($TimeOut -ne 0)) If (Get-CMDevice -CollectionName $DeviceCollection -Name $Name) { Write-SDMLog -Message "MEMCM device $($Name) added to device collection $($DeviceCollection)" -Severity Info } Else { Throw } $ExitLoop = $True } Catch { If ($RetryCount -eq 0) { Write-SDMLog -Message "Failed to add MEMCM device $($Name) to device collection $($DeviceCollection): $($_.Exception.Message)" -Severity Error Break } Else { Write-SDMLog -Message "Failed to add MEMCM device $($Name) to device collection $($DeviceCollection): retrying in 30 seconds..." -Severity Warning Start-Sleep -Seconds 30 $RetryCount = $RetryCount - 1 } } } While ($ExitLoop -eq $False) } } Function Mount-SDMMEMCMSiteCode { <# .SYNOPSIS Maps the MEMCM site code to the MEMCM server. .DESCRIPTION Creates a temporary drive with the same name as the MEMCM site code that is mapped to the MEMCM server. .PARAMETER MEMCMServer Specifies the name of the MEMCM server. .PARAMETER MEMCMSiteCode Specifies the site code name of the MEMCM server. .EXAMPLE Mount-SDMMEMCMSiteCode -MEMCMServer "SERVER01.domain.local" -MEMCMSiteCode "MCM" #> [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$MEMCMServer, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$MEMCMSiteCode ) Try { If (-not ($MEMCMSiteCode[-1] -eq ":")) { $MEMCMSiteCodeWithSuffix = "$($MEMCMSiteCode):" } Else { $MEMCMSiteCodeWithSuffix = $MEMCMSiteCode $MEMCMSiteCode = $MEMCMSiteCode -Replace ".$" } If (-not (Get-PSDrive | Where-Object Name -eq $MEMCMSiteCode)) { Write-SDMLog -Message "Mount MEMCM site code $($MEMCMSiteCode) to server $($MEMCMServer)..." -Severity Info New-PSDrive -Name $MEMCMSiteCode -PSProvider "CMSite" -Root $MEMCMServer -Scope Global | Out-Null If (Get-PSDrive | Where-Object Name -eq $MEMCMSiteCode) { Write-SDMLog -Message "MEMCM site code $($MEMCMSiteCode) mounted to server $($MEMCMServer)" -Severity Info } Else { Throw } } } Catch { Write-SDMLog -Message "Failed to mount the MEMCM site code $($MEMCMSiteCode) to server $($MEMCMServer): $($_.Exception.Message)" -Severity Error Break } } Function Add-SDMMEMCMDeviceCollectionDirectMembershipRule { <# .SYNOPSIS Adds a device to a MEMCM device collection. .DESCRIPTION Adds a device to a MEMCM device collection. .PARAMETER Name Specifies the name of the device. .PARAMETER DeviceCollection Specifies the name of the device collection. .PARAMETER MEMCMServer Specifies the name of the MEMCM server. .PARAMETER MEMCMSiteCode Specifies the site code name of the MEMCM server. .EXAMPLE Add-SDMMEMCMDeviceCollectionDirectMembershipRule -Name "CLIENT01" -DeviceCollection "All client devices" -MEMCMServer "SERVER01.domain.local" -MEMCMSiteCode "MCM" #> [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$Name, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$DeviceCollection, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$MEMCMServer, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$MEMCMSiteCode ) Write-SDMLog -Message "Add MEMCM device $($Name) to device collection $($DeviceCollection)..." -Severity Info Mount-SDMMEMCMSiteCode -MEMCMServer $MEMCMServer -MEMCMSiteCode $MEMCMSiteCode Try { If (-not ($MEMCMSiteCode[-1] -eq ":")) { $MEMCMSiteCodeWithSuffix = "$($MEMCMSiteCode):" } Else { $MEMCMSiteCodeWithSuffix = $MEMCMSiteCode $MEMCMSiteCode = $MEMCMSiteCode -Replace ".$" } Set-Location -Path $MEMCMSiteCodeWithSuffix } Catch { Write-SDMLog -Message "Failed to set the MEMCM site code to $($MEMCMSiteCode): $($_.Exception.Message)" -Severity Error Break } Try { Add-CMDeviceCollectionDirectMembershipRule -CollectionName $DeviceCollection -ResourceId (Get-CMDevice -Name $Name).ResourceID } Catch { Write-SDMLog -Message "Failed to add MEMCM device $($Name) to device collection $($DeviceCollection): $($_.Exception.Message)" -Severity Error Break } $ExitLoop = $False $RetryCount = 5 Do { Try { $DeviceCollectionQuery = Get-WmiObject -Namespace "Root\SMS\Site_$($MEMCMSiteCode)" -Class SMS_Collection -ComputerName $MEMCMServer -Filter "Name='$DeviceCollection'" $DeviceCollectionQuery.RequestRefresh() | Out-Null $TimeOut = 180 #Time-out after 30 minutes Do { $TimeOut = $TimeOut - 1 Start-Sleep -Seconds 10 } While (((Get-CMDevice -CollectionName $DeviceCollection -Name $Name) -eq $Null) -and ($TimeOut -ne 0)) If (Get-CMDevice -CollectionName $DeviceCollection -Name $Name) { Write-SDMLog -Message "MEMCM device $($Name) added to device collection $($DeviceCollection)" -Severity Info } Else { Throw } $ExitLoop = $True } Catch { If ($RetryCount -eq 0) { Write-SDMLog -Message "Failed to add MEMCM device $($Name) to device collection $($DeviceCollection): $($_.Exception.Message)" -Severity Error Break } Else { Write-SDMLog -Message "Failed to add MEMCM device $($Name) to device collection $($DeviceCollection): retrying in 60 seconds..." -Severity Warning Start-Sleep -Seconds 60 $RetryCount = $RetryCount - 1 } } } While ($ExitLoop -eq $False) } Function Add-SDMMEMCMDeviceVariable { <# .SYNOPSIS Adds a variable to a device. .DESCRIPTION Adds a variable to a device. .PARAMETER Name Specifies the name of the device. .PARAMETER Variable Specifies the name of the variable. .PARAMETER Value Specifies the value of the variable. .PARAMETER MEMCMServer Specifies the name of the MEMCM server. .PARAMETER MEMCMSiteCode Specifies the site code name of the MEMCM server. .PARAMETER IsMask Specifies if the value should be masked. .EXAMPLE Add-SDMMEMCMDeviceVariable -Name "CLIENT01" -Variable "Variable01" -Value "Value01" -MEMCMServer "SERVER01.domain.local" -MEMCMSiteCode "MCM" .EXAMPLE Add-SDMMEMCMDeviceVariable -Name "CLIENT01" -Variable "Variable01" -Value "Value01" -MEMCMServer "SERVER01.domain.local" -MEMCMSiteCode "MCM" -IsMask:$True #> [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$Name, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$Variable, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$Value, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$MEMCMServer, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$MEMCMSiteCode, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [Bool]$IsMask = $False ) Write-SDMLog -Message "Add MEMCM device variable $($Variable) with value $($Value) to device $($Name)..." -Severity Info Mount-SDMMEMCMSiteCode -MEMCMServer $MEMCMServer -MEMCMSiteCode $MEMCMSiteCode Try { If (-not ($MEMCMSiteCode[-1] -eq ":")) { $MEMCMSiteCodeWithSuffix = "$($MEMCMSiteCode):" } Else { $MEMCMSiteCodeWithSuffix = $MEMCMSiteCode $MEMCMSiteCode = $MEMCMSiteCode -Replace ".$" } Set-Location -Path $MEMCMSiteCodeWithSuffix } Catch { Write-SDMLog -Message "Failed to set the MEMCM site code to $($MEMCMSiteCode): $($_.Exception.Message)" -Severity Error Break } Try { New-CMDeviceVariable -DeviceName $Name -VariableName $Variable -VariableValue $Value -IsMask $IsMask | Out-Null Do { Start-Sleep -Seconds 5 } Until (((Get-CMDeviceVariable -DeviceName $Name -VariableName $Variable | Select-Object Value).Value) -eq $Value) If (((Get-CMDeviceVariable -DeviceName $Name -VariableName $Variable | Select-Object Value).Value) -eq $Value) { Write-SDMLog -Message "MEMCM device variable $($Variable) with value $($Value) added to device $($Name)" -Severity Info } Else { Throw } } Catch { Write-SDMLog -Message "Failed to add MEMCM device variable $($Variable) with value $($Value) added to device $($Name): $($_.Exception.Message)" -Severity Error } } Function Test-SDMMEMCMDeviceExists { <# .SYNOPSIS Checks if a device exists in MEMCM. .DESCRIPTION Checks if a device exists in MEMCM. .PARAMETER Name Specifies the name of the device. .OUTPUTS Returns a boolean True or False. .EXAMPLE Test-SDMMEMCMDeviceExists -Name "CLIENT01" #> [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$Name ) Try { If (Get-CMDevice -Name $Name) { Return $True } Else { Return $False } } Catch { Write-SDMLog -Message "Failed to check if MEMCM device $($Name) exists: $($_.Exception.Message)" -Severity Error } } #endregion Microsoft EndPoint Manager Configuration Manager #region Windows Function Test-SDMComputerConnection { <# .SYNOPSIS Checks if a computer is reachable. .DESCRIPTION Checks if a computer responds to a ping, UNC and WinRM. .PARAMETER Name Specifies the computer name. .PARAMETER UNC Specifies if UNC reachability should be checked. Default true. .PARAMETER WSMan Specifies if WinRM reachability should be checked. Default false. .PARAMETER Retry Specifies the number of retries. Default 0. .EXAMPLE Test-SDMComputerConnection -Name "CLIENT01" .EXAMPLE Test-SDMComputerConnection -Name "CLIENT01" -Retry 10 .EXAMPLE Test-SDMComputerConnection -Name "CLIENT01" -UNC:$True -WSman:$True -Retry 10 #> [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$Name, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [Switch]$UNC = $True, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [Switch]$WSMan = $False, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [Int]$Retry = 0 ) Write-SDMLog -Message "Test connectivity to device $($Name)..." -Severity Info Try { If (-not (Test-Connection -ComputerName $Name -Count 1 -ErrorAction SilentlyContinue)) { If ($Retry -gt 0) { $Retries = $Retry Do { Write-SDMLog -Message "Failed to ping device $($Name): retrying in 10 seconds..." -Severity Warning $Retries = $Retries - 1 Start-Sleep -Seconds 9 } Until ((Test-Connection -ComputerName $Name -Count 1 -ErrorAction SilentlyContinue) -or ($Retries -le 0)) If ($Retries -le 0) { Write-SDMLog -Message "Failed to ping device $($Name)" -Severity Error Throw } } Else { Write-SDMLog -Message "Failed to ping device $($Name)" -Severity Error Throw } } If ((-not (Test-Path -Path "filesystem::\\$($Name)\C$" -ErrorAction SilentlyContinue)) -and $UNC) { If ($Retry -gt 0) { $Retries = $Retry Do { Write-SDMLog -Message "Failed to connect to filesystem on device $($Name): retrying in 10 seconds..." -Severity Warning $Retries = $Retries - 1 Start-Sleep -Seconds 10 } Until ((Test-Path -Path "filesystem::\\$($Name)\C$" -ErrorAction SilentlyContinue) -or ($Retries -le 0)) If ($Retries -le 0) { Write-SDMLog -Message "Failed to connect to filesystem on device $($Name)" -Severity Error Throw } } Else { Write-SDMLog -Message "Failed to connect to filesystem on device $($Name)" -Severity Error Throw } } If ((-not (Test-WSMan -ComputerName $AzVMHostname -ErrorAction SilentlyContinue)) -and $WSMan) { If ($Retry -gt 0) { $Retries = $Retry Do { Write-SDMLog -Message "The WinRM service is not running on device $($Name): retrying in 10 seconds..." -Severity Warning $Retries = $Retries - 1 Start-Sleep -Seconds 10 } Until ((Test-WSMan -ComputerName $AzVMHostname -ErrorAction SilentlyContinue) -or ($Retries -le 0)) If ($Retries -le 0) { Write-SDMLog -Message "The WinRM service is not running on device $($Name)" -Severity Error Throw } } Else { Write-SDMLog -Message "The WinRM service is not running on device $($Name)" -Severity Error Throw } } Write-SDMLog -Message "Successfully tested connectivity to device $($Name)" -Severity Info } Catch { Write-SDMLog -Message "Failed to test connectivity to device $($Name)" -Severity Error Break } } #endregion Windows #region VMWare Function New-SDMVMGuest { <# .SYNOPSIS Creates a VMWare guest. .DESCRIPTION Creates a VMWare guest. .PARAMETER Name Specifies the name of the VMWare guest. .PARAMETER VMHostClusterName Specifies the name of the VMWare cluster. .PARAMETER VMGuestOS Specifies the operating system that will be installed on the VMWare guest. .PARAMETER VMGuestCPUSockets Specifies the number of virtual CPU's. .PARAMETER VMGuestCPUCores Specifies the number of cores per virtual CPU. .PARAMETER VMGuestMemory Specifies the amount of virtual memory. .PARAMETER VMDatastoreClusterName Specifies the name of the VMWare datastore cluster. .PARAMETER VMGuestDiskSpace Specifies the amount of disk space for the VMWare guest. .PARAMETER VMGuestDiskStorageFormat Specifies the type of disk space storage. .PARAMETER VMGuestVLAN Specifies the number of the VLAN. .PARAMETER VMGuestNotes Specifies notes for the VMWare guest. .PARAMETER VMGuestISOPath Specifies the datastore path to the ISO file. .PARAMETER VMGuestEFIBIOS Specifies if BIOS or EFI is used. Default false. .EXAMPLE New-SDMVMGuest -Name "CLIENT01" -VMGuestOS "windows9_64Guest" -VMGuestCPUSockets 1 -VMGuestCPUCores 4 -VMGuestMemory 16 -VMGuestDiskSpace 80 -VMGuestDiskStorageFormat "Thin" -VMGuestVLAN 10 .EXAMPLE New-SDMVMGuest -Name "CLIENT01" -VMHostClusterName "CLUSTER01" -VMGuestOS "windows9_64Guest" -VMGuestCPUSockets 1 -VMGuestCPUCores 4 -VMGuestMemory 16 -VMDatastoreClusterName "DATASTORECLUSTER01" -VMGuestDiskSpace 80 -VMGuestDiskStorageFormat "Thin" -VMGuestVLAN 10 -VMGuestNotes "VMWare guest system" -VMGuestISOPath = "[VM-DS-01] ISO/Bootable.ISO" -VMGuestEFIBIOS:$False #> [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$Name, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [String]$VMHostClusterName = "", [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$VMGuestOS, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [Int]$VMGuestCPUSockets, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [Int]$VMGuestCPUCores, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [Int]$VMGuestMemory, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [String]$VMDatastoreClusterName = "", [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [Int]$VMGuestDiskSpace, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [ValidateSet("EagerZeroedThick", "Thick", "Thick2GB", "Thin", "Thin2GB")] [String]$VMGuestDiskStorageFormat, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$VMGuestVLAN, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [String]$VMGuestNotes = "", [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [String]$VMGuestISOPath = "", [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [Switch]$VMGuestEFIBIOS = $False ) Write-SDMLog -Message "Creating VMWare guest $($Name)..." -Severity Info If (Get-VM | Where-Object {$_.Name -eq $Name}) { Remove-SDMVMGuest -Name $Name -Confirm:$False } Try { If ($VMHostClusterName -ne "") { $VMHostClusterObject = Get-Cluster -Name $VMHostClusterName $VMHostObject = $VMHostClusterObject | Get-VMHost | Select-Object Name, ConnectionState, PowerState, @{N='Load';E={[Math]::Round((($_.CpuUsageMhz/$_.CpuTotalMhz)*100)+(($_.MemoryUsageGB/$_.MemoryTotalGB)*100))}} | Where-Object {$_.ConnectionState -eq "Connected" -and $_.PowerState -eq "PoweredOn"} | Sort-Object Load | Select-Object -First 1 } Else { $VMHostObject = Get-VMHost | Select-Object Name, ConnectionState, PowerState, @{N='Load';E={[Math]::Round((($_.CpuUsageMhz/$_.CpuTotalMhz)*100)+(($_.MemoryUsageGB/$_.MemoryTotalGB)*100))}} | Where-Object {$_.ConnectionState -eq "Connected" -and $_.PowerState -eq "PoweredOn"} | Sort-Object Load | Select-Object -First 1 } If ($VMDatastoreClusterName -ne "") { $VMDatastoreClusterObject = Get-DatastoreCluster -Name $VMDatastoreClusterName $VMDataStoreObjects = $VMDatastoreClusterObject | Get-Datastore $VMDatastoreObject = $VMDataStoreObjects | Where-Object {$VMDataStoreObjects.Name -eq $_.Name -and $_.State -eq "Available"} | Sort-Object FreeSpaceGB -Descending | Select-Object -First 1 } Else { $VMDatastoreObject = Get-VMHost -Name $VMHostObject.Name | Get-Datastore | Where-Object {$_.State -eq "Available"} | Sort-Object FreeSpaceGB -Descending | Select-Object -First 1 } If (Get-VirtualPortGroup -VMHost $VMHostObject.Name | Where-Object {$_.VLANID -eq $VMGuestVLAN}) { $VMGuestVLANObject = Get-VirtualPortGroup -VMHost $VMHostObject.Name | Where-Object {$_.VLANID -eq $VMGuestVLAN} } Else { Write-SDMLog -Message "VLAN $($VMGuestVLAN) is not available on host $($VMHostObject.Name)" -Severity Error Throw } If (-not (Get-VMHost -Name $VMHostObject.Name | Get-Datastore | Where-Object {$_.State -eq "Available" -and $_.Name -eq ($VMGuestISOPath | Select-String '(?<=\[)[^]]+(?=\])' -AllMatches).Matches.Value})) { Write-SDMLog -Message "ISO path is not available on host $($VMHostObject.Name)" -Severity Error Throw } $VMWareGuest = New-VM -VMHost $VMHostObject.Name -Name $Name -Datastore $VMDatastoreObject -DiskGB $VMGuestDiskSpace -DiskStorageFormat $VMGuestDiskStorageFormat -MemoryGB $VMGuestMemory -NumCpu $VMGuestCPUCores -CD -GuestID $VMGuestOS -NetworkName $VMGuestVLANObject -Notes $VMGuestNotes $VMGuestMACAddress = (Get-VM -Name $Name | Get-NetworkAdapter).MacAddress.ToUpper() $VMWareGuest | Get-NetworkAdapter | Set-NetworkAdapter -Type vmxnet3 -Confirm:$False | Out-Null $VMWareGuest | Get-NetworkAdapter | Set-NetworkAdapter -NetworkName $VMGuestVLANObject -Confirm:$False | Out-Null New-AdvancedSetting -Name devices.hotplug -Value False -Entity $VMWareGuest -Confirm:$False | Out-Null $VMGuestCoresPerSocket = $VMGuestCPUCores / $VMGuestCPUSockets New-AdvancedSetting -Name cpuid.coresPerSocket -Value $VMGuestCoresPerSocket -Entity $VMWareGuest -Confirm:$False | Out-Null $VMWareGuest | Get-CDDrive | Set-CDDrive -IsoPath $VMGuestISOPath -StartConnected $True -Confirm:$False | Out-Null $VMGuestSpec = New-Object VMWare.Vim.VirtualMachineConfigSpec If ($VMGuestEFIBios) { $VMGuestSpec.Firmware = [VMWare.Vim.GuestOsDescriptorFirmwareType]::efi } Else { $VMGuestSpec.Firmware = [VMWare.Vim.GuestOsDescriptorFirmwareType]::bios } $VMWareGuest.ExtensionData.ReconfigVM_Task($VMGuestSpec) | Out-Null If (Get-VM | Where-Object {$_.Name -eq $Name}) { Write-SDMLog -Message "VMWare guest succesfully created with the following settings:" -Severity Info Write-SDMLog -Message "Name..................: $($Name)" -Severity Info Write-SDMLog -Message "Guest OS..............: $($VMGuestOS)" -Severity Info Write-SDMLog -Message "vCPU sockets..........: $($VMGuestCPUSockets)" -Severity Info Write-SDMLog -Message "vCPU cores............: $($VMGuestCPUCores)" -Severity Info Write-SDMLog -Message "Memory GB.............: $($VMGuestMemory)" -Severity Info Write-SDMLog -Message "Disk size GB..........: $($VMGuestDiskSpace)" -Severity Info Write-SDMLog -Message "Disk storage format...: $($VMGuestDiskStorageFormat)" -Severity Info If ($VMGuestEFIBIOS) { Write-SDMLog -Message "BIOS..................: EFI" -Severity Info } Else { Write-SDMLog -Message "BIOS..................: Legacy" -Severity Info } If ($VMHostClusterName -ne "") { Write-SDMLog -Message "Host cluster..........: $($VMHostClusterName)" -Severity Info } Else { $VMHostClusterName = Get-VMHost -Name $VMHostObject.Name | Get-Cluster If ($VMHostClusterName -ne "") { Write-SDMLog -Message "Host cluster..........: $($VMHostClusterName)" -Severity Info } } Write-SDMLog -Message "Host..................: $($VMHostObject.Name)" -Severity Info If ($VMDatastoreClusterName -ne "") { Write-SDMLog -Message "Datastore cluster.....: $($VMDatastoreClusterName)" -Severity Info } Else { If (Get-Datastore -Name $VMDatastoreObject | Get-DatastoreCluster) { $VMDatastoreClusterName = Get-Datastore -Name $VMDatastoreObject | Get-DatastoreCluster Write-SDMLog -Message "Datastore cluster.....: $($VMDatastoreClusterName)" -Severity Info } Else { Write-SDMLog -Message "Datastore cluster.....: N/A" -Severity Info } } Write-SDMLog -Message "Datastore.............: $($VMDatastoreObject)" -Severity Info Write-SDMLog -Message "VLAN..................: $($VMGuestVLAN)" -Severity Info Write-SDMLog -Message "MAC address...........: $($VMGuestMACAddress)" -Severity Info Write-SDMLog -Message "Notes.................: $($VMGuestNotes)" -Severity Info Write-SDMLog -Message "ISO path..............: $($VMGuestISOPath)" -Severity Info } Else { Throw } } Catch { Write-SDMLog -Message "Failed to create a VMWare guest with the name $($Name): $($_.Exception.Message)" -Severity Error Break } } Function Remove-SDMVMGuest { <# .SYNOPSIS Removes a VMWare guest. .DESCRIPTION Removes a VMWare guest. .PARAMETER Name Specifies the name of the VMWare guest. .PARAMETER Confirm Specifies if a confirmation is needed. Default true. .EXAMPLE Remove-SDMVMGuest -Name "CLIENT01" .EXAMPLE Remove-SDMVMGuest -Name "CLIENT01" -Confirm:$False #> [CmdletBinding()] Param ( [Parameter(Mandatory = $True,ValueFromPipeline = $True)] [ValidateNotNullOrEmpty()] [String]$Name, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [Switch]$Confirm = $True ) Process { $Result = Get-VM | Where-Object {$_.Name -eq $Name} If ($Result) { If ($Result.PowerState -eq "PoweredOn") { Try { Write-SDMLog -Message "Powering off VMWare guest $($Name)..." -Severity Info Stop-VM -VM $Name -Confirm:$Confirm | Out-Null $TimeOut = 12 #Time-out after 1 minute Do { $TimeOut = $TimeOut - 1 Start-Sleep -Seconds 5 $Result = Get-VM | Where-Object {$_.Name -eq $Name} } While (($Result.PowerState -eq "PoweredOn") -and ($TimeOut -ne 0)) If ($Result.PowerState -eq "PoweredOff") { Write-SDMLog -Message "VMWare guest $($Name) is powered off" -Severity Info } Else { Throw } } Catch { Write-SDMLog -Message "Failed to power off VMWare guest $($Name): $($_.Exception.Message)" -Severity Error Break } } Try { Write-SDMLog -Message "Removing VMWare guest $($Name)..." -Severity Info Remove-VM -DeletePermanently -VM $Name -Confirm:$Confirm $TimeOut = 12 #Time-out after 1 minute Do { $TimeOut = $TimeOut - 1 Start-Sleep -Seconds 5 $Result = Get-VM | Where-Object {$_.Name -eq $Name} } While (($Result) -and ($TimeOut -ne 0)) If (-not ($Result)) { Write-SDMLog -Message "VMWare guest $($Name) is removed" -Severity Info } Else { Throw } } Catch { Write-SDMLog -Message "Failed to remove VMWare guest $($Name): $($_.Exception.Message)" -Severity Error Break } } } } #endregion VMWare #region Logging Function Write-SDMLog { <# .SYNOPSIS Displays log messages and writes them to a log file. .DESCRIPTION Displays log messages and writes them to a log file. .PARAMETER Message Specifies the message. .PARAMETER Severity Specifies the severity of the message. .PARAMETER Path Specifies the path where the log file is saved. .PARAMETER SkipLogFileRotation No log file rotation and cleanup will be done. Default false. .EXAMPLE Write-SDMLog -Message "This is a log message" -Severity Info .EXAMPLE Write-SDMLog -Message "This is a log message" -Severity Info -Path "C:\Logfile.log" -SkipLogFileRotation:$True #> [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$Message, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [ValidateSet("Info", "Warning", "Error")] [String]$Severity, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [String]$Path = $Global:LogFilePath, [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] [Switch]$SkipLogFileRotation = $False ) $DateLogFile = Get-Date -Format "MM-dd-yyyy" $DateConsole = Get-Date -Format "dd-MM-yyyy" $TimeLogFile = Get-Date -Format "HH:mm:ss.ffffff" $TimeConsole = Get-Date -Format "HH:mm:ss" $DateArchive = Get-Date -Format "yyyyMMdd" $TimeArchive = Get-Date -Format "HHmmss" $Context = $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) If ($Null -eq $MyInvocation.PSCommandPath) { $Source = "Script" } Else { $Source = Split-Path -Path $MyInvocation.PSCommandPath -Leaf } $Component = Get-SDMFunctionName -StackNumber 2 Switch ($Severity) { "Info" {[Int]$Severity = 1} "Warning" {[Int]$Severity = 2} "Error" {[Int]$Severity = 3} } If ($PSBoundParameters.ContainsKey("Path") -or ($Path -ne "")) { If (-not (Test-Path -Path $Path)) { Try { New-Item -Path $Path -ItemType File | Out-Null } Catch { If (Test-SDMAzureRunbookConsole) { Write-Output "[$($DateConsole) $($TimeConsole)] - [W]: Unable to create log file $($Path): $($_.Exception.Message)" } Else { Write-Host "[$($DateConsole) $($TimeConsole)]: Unable to create log file $($Path): $($_.Exception.Message)" -ForegroundColor Yellow } } } $LogText = "<![LOG[$($Message)]LOG]!><time=""$($TimeLogFile)"" date=""$($DateLogFile)"" component=""$($Component)"" context=""$($Context)"" type=""$($Severity)"" thread=""$($PID)"" file=""$($Source)"">" Try { Add-Content -Value $LogText -Path $Path } Catch { If (Test-SDMAzureRunbookConsole) { Write-Output "[$($DateConsole) $($TimeConsole)] - [W]: Unable to write to log file $($Path): $($_.Exception.Message)" } Else { Write-Host "[$($DateConsole) $($TimeConsole)]: Unable to write to log file $($Path): $($_.Exception.Message)" -ForegroundColor Yellow } } #No log file rotation and cleanup will be done when using the -SkipLogFileRotation parameter. If (-not ($SkipLogFileRotation)) { $LogFileSize = (Get-Item -Path $Path).Length/1MB $LogFileDirectoryName = (Get-Item -Path $Path).DirectoryName $LogFileBaseName = (Get-Item -Path $Path).BaseName $LogFileExtension = (Get-Item -Path $Path).Extension $LogFileArchiveSuffix = "-$($DateArchive)-$($TimeArchive)" #Archive log file when exceeding the maximum log file size If ($LogFileSize -gt $Global:LogFileSize) { Try { Rename-Item -Path $Path -NewName "$($LogFileBaseName)$($LogFileArchiveSuffix)$($LogFileExtension)" Write-SDMLog -Message "Previous log file has been archived in $($LogFileBaseName)$($LogFileArchiveSuffix)$($LogFileExtension)" -Severity Info -Path $Path -SkipLogFileRotation } Catch { Write-SDMLog -Message "Unable to archive log file $($Path): $($_.Exception.Message)" -Severity Warning -Path $Path -SkipLogFileRotation } } #Cleanup old archived log files when exceeding the maximum number $LogFileArchiveCount = (Get-ChildItem -Path $LogFileDirectoryName -Filter "$($LogFileBaseName)-????????-??????$($LogFileExtension)").Count If ($LogFileArchiveCount -gt $Global:LogFileArchiveNumber) { $LogFileArchiveExpiredCount = ($LogFileArchiveCount - $Global:LogFileArchiveNumber) $LogFileArchiveToDelete = (Get-ChildItem -Path $LogFileDirectoryName -Filter "$($LogFileBaseName)-????????-??????$($LogFileExtension)" | Select-Object Name -First $LogFileArchiveExpiredCount | Sort-Object Name).Name Try { ForEach ($LogFile In $LogFileArchiveToDelete) { Remove-Item -Path "$($LogFileDirectoryName)\$($LogFile)" Write-SDMLog -Message "Delete old archived log file $($LogFile)" -Severity Info -Path $Path -SkipLogFileRotation } } Catch { Write-SDMLog -Message "Unable to delete old archive log files: $($_.Exception.Message)" -Severity Warning -Path $Path -SkipLogFileRotation } } } } #Write logging to the console Switch ($Severity) { 1 { If (Test-SDMAzureRunbookConsole) { $Command = "Write-Output '[$($DateConsole) $($TimeConsole)] - [I]: $($Message)'" } Else { $Command = "Write-Host [$($DateConsole) $($TimeConsole)]: $($Message) -ForegroundColor Green -BackgroundColor Black" } } 2 { If (Test-SDMAzureRunbookConsole) { $Command = "Write-Output '[$($DateConsole) $($TimeConsole)] - [W]: $($Message)'" } Else { $Command = "Write-Host [$($DateConsole) $($TimeConsole)]: $($Message) -ForegroundColor Yellow -BackgroundColor Black" } } 3 { If (Test-SDMAzureRunbookConsole) { $Command = "Write-Output '[$($DateConsole) $($TimeConsole)] - [E]: $($Message)'" } Else { $Command = "Write-Host [$($DateConsole) $($TimeConsole)]: $($Message) -ForegroundColor Red -BackgroundColor Black" } } } Invoke-Expression -Command $Command } Function Get-SDMFunctionName { [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [Int]$StackNumber ) $Result = $(Get-PSCallStack)[$StackNumber].FunctionName Switch ($Result) { "<ScriptBlock>" {Return "ScriptBlock"} Default {Return $Result} } } #endregion Logging #region Azure Function Test-SDMAzureRunbookConsole { If ($PSPrivateMetadata.JobId) { Return $True } Else { Return $False } } #endregion Azure #region General Function New-SDMRandomString { <# .SYNOPSIS Creates a random string with predefined characters. .DESCRIPTION Creates a random string with predefined characters. .PARAMETER Characters Specifies the characters used to create the random string. .PARAMETER Length Specifies the length of the random string. .EXAMPLE New-SDMRandomString -Characters "abcdef0123456789" -Length 8 .EXAMPLE New-SDMRandomString -Characters "abcdefABCDEF0123456789+-!?" -Length 12 #> [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [String]$Characters, [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [Int]$Length ) $Chars = $Characters.ToCharArray() $String = "" 1..$Length | ForEach {$String += $Chars | Get-Random} Return $String } #endregion General Export-ModuleMember -Function Import-SDMMEMCMModule, Import-SDMMEMCMDevice, Mount-SDMMEMCMSiteCode, Add-SDMMEMCMDeviceCollectionDirectMembershipRule, Add-SDMMEMCMDeviceVariable, Test-SDMMEMCMDeviceExists, Test-SDMComputerConnection, New-SDMVMGuest, Remove-SDMVMGuest, Write-SDMLog, New-SDMRandomString |