<# ################ .SYNOPSIS A short one-line action-based description, e.g. 'Tests if a function is valid' .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .NOTES Information or caveats about the function e.g. 'This function is not supported in Linux' .LINK Specify a URI to a help page, this will show when Get-Help -Online is used. .EXAMPLE $NewVMWindowsAzureParams = @{ ComputerName = 'itdzmtest701.nd.gov'; ResourceGroupNameOverride = 'rg-shared-iis-tst'; AvailabilityZone = 2; CPU = 2; MemoryGB = 16; DiskOsGB = 128; DiskDataGB = 0; Subnet = '10.21.8.0/22'; OS = 'Windows Server 2022 Datacenter'; Environment = 'Test'; AppName = 'ITD-POC-zmeier'; LicensingRestrictions = "No Licensing Restrictions"; } New-ITDWindowsVmAzure @NewVMWindowsAzureParams -Credential $PrvCred -Verbose #> function New-ITDWindowsVmAzure { [CmdletBinding()] param ( [string] $ComputerName, [string] $ResourceGroupNameOverride, [string] $AppName, [ValidateSet(1, 2, 3)] [int] $AvailabilityZone, [int] $CPU, [int] $MemoryGB, [int] $DiskOsGB, [int] $DiskDataGB, [string] $Subnet, [string] $OS, [string] $Environment, #[string] #$Subscription, [string] $LicensingRestrictions, [PSCredential] $Credential ) begin { } process { $ComputerName = $ComputerName.ToLower() $FQDN = $ComputerName $Hostname = $FQDN.split('.')[0] Write-Verbose -Message "Prepare Connections" #$tenantId = '2dea0464-da51-4a88-bae2-b3db94bc0c54' #$AppId = '60244573-7130-4026-9c6d-47de73f8ca29' #$SecureStringPwd = '' #$Secret:AzureVMServicePrincipal #Pqt8Q~E-dDmQugcPPWdaK2t_4retS41VVVVOZbOx #$SecureStringPwd = 'Pqt8Q~E-dDmQugcPPWdaK2t_4retS41VVVVOZbOx' #$PSCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppId, ($SecureStringPwd | ConvertTo-SecureString -AsPlainText -Force) #Connect-AzAccount -ServicePrincipal -Credential $PSCredential -Tenant $tenantId Write-Verbose -Message "Prepare Credentials" $RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password)) Write-Verbose -Message "Infoblox: Find DNS pre-existing record, or create one" Clear-DnsClientCache $Cidr = $Subnet [Net.IpAddress]$NetworkId = $Cidr.split('/')[0] ####### ####### Remove 10.10.10.10 references when DNS sync is fixed ####### [Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -Server 10.10.10.10 -ErrorAction SilentlyContinue).IPAddress $SubnetMaskInt = $CIDR.split('/')[1] $Int64 = ([convert]::ToInt64(('1' * $SubnetMaskInt + '0' * (32 - $SubnetMaskInt)), 2)) [Net.IPAddress]$SubnetMask = '{0}.{1}.{2}.{3}' -f ([math]::Truncate($Int64 / 16777216)).ToString(), ([math]::Truncate(($Int64 % 16777216) / 65536)).ToString(), ([math]::Truncate(($Int64 % 65536) / 256)).ToString(), ([math]::Truncate($Int64 % 256)).ToString() $IPSplit = $Subnet.Split('.') [Net.IPAddress]$DefaultGateway = ($IPSplit[0] + '.' + $IPSplit[1] + '.' + $IPSplit[2] + '.' + (($CIDR.split('/')[0].split('.')[-1] -as [int]) + 1) ) If ($null -ne $IpAddress) { If (($IpAddress.Address -band $SubnetMask.Address) -eq ($NetworkId.Address -band $SubnetMask.Address)) { Write-Warning "DNS record already exists, CIDR Block match" } Else { Write-Error "DNS record already exists, and does not match CIDR Block" Break } } Else { Write-Verbose -Message "Pre-existing IP address not found, creating new DNS record." New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred Start-Sleep -Seconds 5 Write-Verbose -Message ("FQDN is " + $FQDN) ####### ####### Remove 10.10.10.10 references when DNS sync is fixed ####### #[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction Stop -Server 10.10.10.10).IPAddress [Net.IPAddress]$IpAddress = (Get-ITDIbDNSRecord -Hostname $FQDN -Credential $RadiusCred).IPv4Address If ((Test-NetConnection -ComputerName $IpAddress.IPAddressToString).PingSucceeded) { Write-Error "IP Address already in use." -ErrorAction Stop } } Write-Verbose -Message "Passwordstate: If local administrator password does not exist in vault, create it." If ($FQDN -like "itdcnd*") { $PasswordStateList = "Peoplesoft Share PW" } Else { $PasswordStateList = "CSRC" } $GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential -ErrorAction SilentlyContinue If ($GuestVMLocalCredential) { Write-Verbose -Message "Passwordstate: Local admin password record already exists, use those credentials" } Else { Write-Verbose -Message "Passwordstate: Local admin password record does not exist, creating new credentials" $GuestVMLocalCredential = New-ITDPassword -Title $FQDN -UserName itdadmin -Description 'Local Administrator' -PasswordList $PasswordStateList -Credential $Credential } $GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force)) $GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force)) Write-Verbose -Message "Determine environment" switch ($Environment) { { $_ -eq 'Test' -or $_ -eq 'Development' } { Write-Verbose -Message "Environment is Test or Development" $EnvShortString = 'tst' } 'Production' { Write-Verbose -Message "Environment is Production" $EnvShortString = 'prd' } } Write-Verbose -Message "Determine subscription, via Subnet" $AllSubscriptions = Get-AzSubscription $AllAzVirtualNetworks = ForEach ($Subscription in $AllSubscriptions) { Set-AzContext -SubscriptionObject $Subscription | Out-Null Get-AzVirtualNetwork | ForEach-Object { [PSCustomObject]@{ Subscription = $Subscription.Name; VirtualNetwork = $_ } } } $Subscription = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).Subscription $VirtualNetwork = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).VirtualNetwork $VirtualNetworkSubnet = $VirtualNetwork.Subnets | Where-Object { $_.AddressPrefix -match $Subnet } $VNetName = $VirtualNetwork.Name $VNetSubnet = $VirtualNetworkSubnet.Name switch ($OS) { "Windows Server 2019 Datacenter" { $VMOS = "Windows" $Publisher = "MicrosoftWindowsServer" $Offer = "WindowsServer" $sku = "2019-Datacenter" } "Windows Server 2022 Datacenter" { $VMOS = "Windows" $Publisher = "MicrosoftWindowsServer" $Offer = "WindowsServer" $sku = "2022-datacenter" } Default { Write-Error "Invalid operating system" -ErrorAction Stop } } # finalize VM location and size $location = "centralus" $VMSizeFilter1 = @( "Standard_D2ds_v5", "Standard_D4ds_v5", "Standard_D8ds_v5", "Standard_D16ds_v5", "Standard_D32ds_v5", "Standard_E2ds_v5", "Standard_E4ds_v5", "Standard_E8ds_v5", "Standard_E16ds_v5", "Standard_E20ds_v5", "Standard_E32ds_v5", "Standard_F2s_v2", "Standard_F4s_v2", "Standard_F8s_v2", "Standard_F16s_v2", "Standard_F32s_v2" ) $VMSize = Get-AzVMSize -Location centralus | ` Where-Object { $VMSizeFilter1 -contains $_.Name } | ` Where-Object { $_.NumberOfCores -ge $CPU -and $_.MemoryInMB -ge ($Memory * 1024) } | ` Where-Object Name -NotMatch "_Promo" | ` Sort-Object NumberOfCores, MemoryInMB | ` Select-Object -First 1 Write-Verbose -Message "Determine ResourceGroupName, and create Resource Group if needed" If ($PSBoundParameters.ContainsKey('ResourceGroupNameOverride')) { # use the name in the variable Write-Verbose -Message "ResourceGroupName parameter found, is $ResourceGroupNameOverride" $ResourceGroupName = $ResourceGroupNameOverride } Else { Write-Verbose -Message "ResourceGroupName parameter not found, determine now" # if name is not given, determine the name $VMOwner = $AppName.split('-')[0].ToLower() $VMFunction = (($AppName -replace "$VMOwner-") -replace "-").ToLower() -replace " " $ResourceGroupName = "rg-$VMOwner-$VMFunction-$EnvShortString" } Write-Verbose -Message "ResourceGroupName is $ResourceGroupName" Write-Verbose -Message "Change to selected subscription and validate resource group exists" Set-AzContext $Subscription | Out-Null $ResGroupExist = $null $ResGroupExist = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue If (@($ResGroupExist).count -gt 1) { Write-Error "Multiple Resource Groups matched" -ErrorAction Stop } If (@($ResGroupExist).count -lt 1) { Write-Warning "No matching resource group found, creating it now" New-AzResourceGroup -Name $ResourceGroupName -Location $Location -Tag @{ApplicationName = $AppName } } If (@($ResGroupExist).count -eq 1) { Write-Verbose -Message "Exactly one matching resource group found" } $VMName = "vm-$HostName-$EnvShortString" $VMIPConfigName = "ipconfig-$HostName-$EnvShortString" $VMNicName = "nic-$HostName-$EnvShortString" $VMObjectName = "vm-$HostName-$EnvShortString" $VMOsDiskName = "vm-$HostName-os-$EnvShortString" Write-Verbose -Message "Verifying Applicable Inputs are Lowercase" $VMIPConfigName = $VMIPConfigName.ToLower() $VMNicName = $VMNicName.ToLower() $VMObjectName = $VMObjectName.ToLower() $VMOsDiskName = $VMOSDiskName.ToLower() Write-Verbose -Message "Creating IPConfig and NIC" $IPConfig = New-AzNetworkInterfaceIpConfig -Name $VMIPConfigName -PrivateIpAddress $IPAddress.IPAddressToString -PrivateIpAddressVersion IPv4 -Subnet $VirtualNetworkSubnet $VMNIC = New-AzNetworkInterface -IpConfigurationName $IPConfig -Location $Location -Name $VMNICName -ResourceGroupName $ResourceGroupName -Subnet $VirtualNetworkSubnet -Force $VMNIC.IpConfigurations[0].PrivateIpAllocationMethod = "Static" $VMNIC.IpConfigurations[0].PrivateIpAddress = $IPAddress.IPAddressToString Set-AzNetworkInterface -NetworkInterface $VMNIC Write-Verbose -Message "Build VM Config" $vmConfigParams = @{ VMName = $VMObjectName VMSize = $VMSize.Name } If ($PSBoundParameters.ContainsKey('AvailabilityZone')) { Write-Verbose -Message "AvailabilityZone parameter found, adding to vmConfigParams" $vmConfigParams += @{ Zone = $AvailabilityZone } } $vmConfig = New-AzVMConfig @vmConfigParams $vmConfig | Set-AzVMOSDisk -Name $VMOSDiskName -CreateOption FromImage If ($VMOS -eq "Windows") { $vmConfig | Set-AzVMOperatingSystem -Windows -ComputerName $VMName -Credential $GuestVMLocalCredential $vmConfig | Set-AzVMSourceImage -PublisherName $Publisher -Offer $Offer -Skus $Sku -Version latest $vmConfig.OSProfile.ComputerName = $HostName } If ($VMOS -eq "Linux") { $vmConfig | Set-AzVMOperatingSystem -Linux -ComputerName $VMFQDN -Credential $GuestVMLocalCredential Switch ($VMSubscription) { "npd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/76297098-764c-43de-8525-c9fda1b237be/resourceGroups/rg-infra-templates-tst-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-103" } "infra01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/e53aa0c7-824d-40a2-b420-4ab77b1051d2/resourceGroups/rg-infra-templates-prd-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-403" } "prd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/437b2bfa-850e-4464-b6c2-38a68cda7c69/resourceGroups/rg-infra-templates-prd-002/providers/Microsoft.Compute/images/vm-rhel74template-prd-003" } } } $vmConfig | Add-AzVMNetworkInterface -Id $VMNIC.ID Set-AzVMBootDiagnostic -VM $vmConfig -Enable -ResourceGroupName $ResourceGroupName Write-Verbose "Creating VM" New-AzVM -VM $vmConfig -ResourceGroupName $ResourceGroupName -Location $Location -DisableBginfoExtension -LicenseType "Windows_Server" #-AsJob Start-Sleep -Seconds 60 $VM = Get-AzVM -Name $VMObjectName -ResourceGroupName $ResourceGroupName If ($PSBoundParameters.ContainsKey("DiskDataGB")) { $ExistingDisks = @($VM.StorageProfile.DataDisks | Select-Object *, @{n = 'ItdId'; e = { [int]($_.Name -replace "vm-$hostname-app-$environment-") } }) $NewDiskItdIdInt = ($ExistingDisks | Sort-Object ItdId -Descending | Select-Object -First 1).ItdId + 1 $NewDiskItdIdStr = $NewDiskItdIdInt.ToString("000") $NewDiskName = "vm-$Hostname-app-$EnvShortString-$NewDiskItdIdStr" #vm-itduc4p1-app-tst-001 $LunID = ($ExistingDisks | Sort-Object Lun -Descending | Select-Object -First 1).Lun + 1 $count = 0 If ($ExistingDisks) { while ($Size -match $ExistingDisks.DiskSizeGB) { $count++ Write-Verbose -Message "SizeGB: $Size, Count: $count" If ($count -ge 11) { Write-Error "Disk size not available" -ErrorAction Stop } Else { $Size = $Size - 1 } } } Write-Verbose -Message "SizeGB: $Size, Count: $count" $AzureRmDiskConfigParams = @{ DiskSizeGB = $DiskDataGB Location = $Location CreateOption = "Empty" SkuName = "Premium_LRS" } If ($Zone) { Write-Verbose "VM is located in Zone $Zone" $AzureRmDiskConfigParams += @{Zone = $Zone } } #$DiskConfig = New-AzureRmDiskConfig -DiskSizeGB $Size -Location $Location -CreateOption Empty -SkuName Premium_LRS $DiskConfig = New-AzDiskConfig @AzureRmDiskConfigParams If (!(Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $NewDiskName -ErrorAction SilentlyContinue)) { $NewDisk = New-AzDisk -DiskName $NewDiskName -Disk $DiskConfig -ResourceGroupName $ResourceGroupName $VM = Add-AzVMDataDisk -Name $NewDiskName -CreateOption Attach -ManagedDiskId $NewDisk.Id -VM $VM -Lun $LunID -Caching ReadOnly Update-AzVM -VM $VM -ResourceGroupName $ResourceGroupName -AsJob } } Write-Verbose -Message "Wait two minutes before Pre-Firewall Guest OS customization" Start-Sleep -Seconds 120 Write-Verbose -Message "Begin Pre-Firewall Guest OS customization" $InvokeAzVMRunCommandParams = @{ ResourceGroupName = $ResourceGroupName; Name = $VMName; CommandId = 'RunPowerShellScript' } Write-Verbose -Message "1-Set WMI Tags" $ScriptBlock = { try { Write-Verbose "Create new Class" $Class = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null); $Class["__CLASS"] = "ITD"; $Class.Qualifiers.Add("Static", $true) $Class.Properties.Add("MyKey", [System.Management.CimType]::String, $false) $Class.Properties["MyKey"].Qualifiers.Add("Key", $true) $Class.Properties.Add("LastModified", [System.Management.CimType]::String, $false) $Class.Properties.Add("DTAP", [System.Management.CimType]::String, $false) $Class.Properties.Add("Baseline", [System.Management.CimType]::String, $false) $Class.Put() Write-Verbose "Create single ITD Object" Set-WmiInstance -Class ITD -Arguments @{LastModified = (Get-Date); DTAP = "Prod"; Baseline = "000" } } catch { Throw $_ Break } } Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock Write-Verbose -Message "3-Disk Configuration" $ScriptBlock = { try { # Non-initialized and MBR-initialized disks will have 0 partitions by default, but GPT-initialized disks will have 1 system reserved partition by default $disks = Get-Disk | Where-Object { $_.NumberOfPartitions -eq 0 -or ( $_.PartitionStyle -eq 'GPT' -and $_.NumberOfPartitions -eq 1 ) } | Sort-Object -Property Number if ($disks) { Write-Verbose -Message "Found $(@($disks).Count) unpartitioned disks." # Prevent the "You must format this partition before using it." popup if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Stop-Service ShellHWDetection -ErrorAction SilentlyContinue } foreach ($disk in $disks) { if ($disk.IsOffline) { Set-Disk $disk.Number -IsOffline $false Write-Verbose -Message "Brought disk $($disk.Number)($("{0:n0}GB" -f ($disk.Size / 1GB))) online..." } if ($disk.IsReadOnly) { Set-Disk $disk.Number -IsReadOnly $false } if ($disk.PartitionStyle -eq 'RAW') { Initialize-Disk $disk.Number -PartitionStyle GPT -ErrorAction SilentlyContinue } $diskParam = @{ FileSystem = 'NTFS' Confirm = $false } $driveLetter = [Int][Char]'D' while (Get-Volume -DriveLetter $([Char]$driveLetter) -ErrorAction SilentlyContinue) { $driveLetter++ } $diskParam.DriveLetter = [Char]$driveLetter if (@($disks).IndexOf($disk) -eq 0 -and (-not (Get-Volume -DriveLetter D -ErrorAction SilentlyContinue))) { $diskParam.NewFileSystemLabel = 'Temporary Storage' } elseif (@($disks).IndexOf($disk) -eq 1 -and (-not (Get-Volume -DriveLetter E -ErrorAction SilentlyContinue))) { $diskParam.NewFileSystemLabel = 'Data' } [void](New-Partition -DiskNumber $disk.Number -DriveLetter $diskParam.DriveLetter -UseMaximumSize) [void](Format-Volume @diskParam) } } else { Write-Verbose -Message "No unpartitioned disks found, continuing..." } } catch { Throw $_ Break } finally { if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Start-Service ShellHWDetection -ErrorAction SilentlyContinue } } } Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock Write-Verbose -Message "5-Time Zone" $ScriptBlock = { if ((Get-TimeZone).Id -ne 'Central Standard Time') { Write-Verbose -Message "Current time zone set to $((Get-TimeZone).Id), setting to Central Standard Time." Set-TimeZone -Id 'Central Standard Time' Write-Verbose -Message "Time zone set to Central Standard Time." } else { Write-Verbose -Message "Time zone is already set to Central Standard Time." } } Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock Write-Verbose -Message "6-Enable Performance Counters" $ScriptBlock = { # Enable Performance Counters try { if (Get-ScheduledTask -TaskName "Server Manager Performance Monitor" | Where-Object State -NE "Running" -ErrorAction SilentlyContinue) { Enable-ScheduledTask -TaskPath "\Microsoft\Windows\PLA\" -TaskName "Server Manager Performance Monitor" | Start-ScheduledTask Write-Verbose -Message "Performance monitors enabled." } else { Write-Verbose -Message "Performance monitors already enabled, continuing..." } } catch { Throw $_ Break } } Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock Write-Verbose -Message "7-Disable Windows Firewall" $ScriptBlock = { # Disable Windows Firewall Write-Verbose -Message "Checking for active Windows Firewall..." if ((Get-NetFirewallProfile).Enabled -contains 'True') { Write-Verbose -Message "Windows Firewall is still enabled, disabling it..." Set-NetFirewallProfile -Profile Domain, Public, Private -Enabled False Write-Verbose -Message "Windows Firewall disabled." } else { Write-Verbose -Message "Windows Firewall already disabled, continuing..." } } Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock # determine Active Directory Forest and OU Write-Verbose -Message "8a-Determine domain join" $DomainName = $FQDN.Substring($FQDN.IndexOf(".") + 1) switch ($DomainName) { 'nd.gov' { $SearchBaseDomain = "dc=nd,dc=gov" } 'ndcloud.gov' { $SearchBaseDomain = "dc=ndcloud,dc=gov" } } If ($DomainName -eq "nd.gov") { $OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq $AppName } If (!($OUAppName)) { $OUAppName = Get-ADOrganizationalUnit -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq 'All-General' } } $ExistingADComputer = Get-ADComputer -Filter { Name -eq $Hostname } If ($ExistingADComputer) { If ($ExistingADComputer.DistinguishedName -like ("*" + $AppName + "*") -or $ExistingADComputer.DistinguishedName -like "*All-General*") { Write-Warning "AD object already exists, OU path does match" $OuFinal = $ExistingADComputer.DistinguishedName -replace '^.+?(? $ScriptBlock = { Param( [string] $DomainName, [string] $OuPath ) Write-Host $OuPath Test-NetConnection -ComputerName nd.gov $DomainJoinCred = New-Object System.Management.Automation.PSCredential('ndgov\svcitdvmdomainjoin', ('hypes-Vgv8h89' | ConvertTo-SecureString -AsPlainText -Force)) Add-Computer -DomainName $DomainName -OUPath $OuPath -Credential $DomainJoinCred Restart-Computer -Force } Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock -Parameter @{ "DomainName" = 'nd.gov'; "OuPath" = "$OuFinal" } } } Start-Sleep -Seconds 120 Write-Verbose -Message "10a-Copy SCCM client from itdsccmp2.nd.gov" -Verbose $ScriptBlock = { #### NEEDS WORK Param( ) Copy-Item -path "\\itdsccmp2.nd.gov\SCCM_Client\Client\" -Destination C:\temp\SCCM_Client -Recurse } $CopySuccess = $false $CopyAttempts = 0 While ($CopySuccess -eq $false -and $CopyAttempts -lt 100) { $Attempts++ $InvokeResult = Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock If ($InvokeResult.value.message -like "Copy-Item : Cannot find path*") { Write-Verbose -Message "SCCM Client file copy failed. Looping until it works." -Verbose $CopySuccess = $false } Else { Write-Verbose -Message "SCCM Client file copy success." -Verbose $CopySuccess = $true } } # Check if SCCM automatically installed the SCCM client and registered it $CcmRegistered = $false $CcmRegistration = Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString { Get-Content C:\Windows\CCM\Logs\ClientIDManagerStartup.log | Select-String RegTask } If ($CcmRegistration.value.message -match "Client is registered") { Write-Verbose "Client is registered." $CcmRegistered = $true } ElseIf ($CcmRegistration.value.message -match "Client is already registered") { Write-Verbose "Client is already registered." $CcmRegistered = $true } If ($CcmRegistered -eq $false) { Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString { If (Get-Process -Name ccmsetup -ErrorAction SilentlyContinue) { Write-Warning "CCM client is already installing" $CcmRegistered = $true } ElseIf (Get-Process -Name ccmexec -ErrorAction SilentlyContinue) { Write-Warning "CCM client is already installed" $CcmRegistered = $true } Else { Write-Warning -Message "Installing SCCM Client..." Invoke-Expression -Command "C:\temp\SCCM_Client\ccmsetup.exe SMSSITECODE=ITD SMSMP=itdsccmp2.nd.gov DNSSUFFIX=nd.gov" } } } Write-Verbose -Message "[$FQDN]:Approve SCCM Client" #Start-Sleep -Seconds 30 # ADD LOOP/SMARTS TO WAIT FOR DISCOVERY AND ANOTHER FOR APPROVAL Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname -ScriptBlock { Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1' $PSDrives = Get-PSDrive If ($PSDrives | Where-Object Name -EQ "ITD") { # ITD Drive exists, do nothing } else { New-PSDrive -Name "ITD" -PSProvider AdminUI.PS.Provider\CMSite -Root itdsccmp2.nd.gov } Set-Location ITD:\ $Device = Get-CMDevice -Name $args[0] If ($Device.IsApproved -eq 0) { Approve-CMDevice -DeviceName $args[0] } } } end { } }