NTS-ConfigMgrTools.psm1
function New-VMVolume { <# .Description Creates a storage pool, virtual disk and volume intended for VMs RAID Level will always be 0, so be carefull .Parameter VMDriveLetter Letter for the volume .Parameter StoragePoolName Name of the StoragePool .Parameter VirtualDiskName Name of the VirtualDisk .Parameter VMVolumeName Name of the Volume .Example # Creates a volume with the letter W New-VMVolume -VMDriveLetter 'W' .NOTES - There must be at least one other disk in addition to disk 0. - If an NVMe disk is present, only this is taken #> param ( [Parameter(Mandatory = $false)] [char] $VMDriveLetter = 'V', [Parameter(Mandatory = $false)] [string] $StoragePoolName = "Pool01", [Parameter(Mandatory = $false)] [string] $VirtualDiskName = "VDisk01", [Parameter(Mandatory = $false)] [string] $VMVolumeName = "VMs" ) try { if ($null -eq (Get-StoragePool -FriendlyName $StoragePoolName -ErrorAction SilentlyContinue)) { $PhysicalDisks = Get-PhysicalDisk -CanPool $true | Where-Object -FilterScript { $PSitem.Bustype -ne "USB" } $NVMe_Devices = $PhysicalDisks | Where-Object -FilterScript { $PSItem.Bustype -eq "NVMe" -and $PSitem.Size -gt 256GB } $Non_NVMe_Devices = $PhysicalDisks | Where-Object -FilterScript { $PSItem.Bustype -ne "NVMe" } if ($null -ne $NVMe_Devices) { $SelectedDisks = $NVMe_Devices } else { $SelectedDisks = $Non_NVMe_Devices } $StorageSubSystemFriendlyName = (Get-StorageSubSystem -FriendlyName "*Windows*").FriendlyName Write-Output "create storage pool '$($StoragePoolName)'" New-StoragePool -StorageSubSystemFriendlyName $StorageSubSystemFriendlyName -FriendlyName $StoragePoolName -PhysicalDisks $SelectedDisks | Out-Null if ($null -eq (Get-VirtualDisk -FriendlyName $VirtualDiskName -ErrorAction SilentlyContinue)) { Write-Output "create vdisk '$($VirtualDiskName)'" New-VirtualDisk -StoragePoolFriendlyName $StoragePoolName -FriendlyName $VirtualDiskName -UseMaximumSize -ProvisioningType Fixed -ResiliencySettingName Simple | Out-Null Initialize-Disk -FriendlyName $VirtualDiskName -PartitionStyle GPT | Out-Null $VDiskNumber = (Get-Disk -FriendlyName $VirtualDiskName).Number Write-Output "create volume '$($VMVolumeName)'" New-Volume -DiskNumber $VDiskNumber -FriendlyName $VMVolumeName -FileSystem ReFS -DriveLetter $VMDriveLetter | Out-Null } else { Write-Output "Virtual disk '$($VirtualDiskName)' already exists - skipping" } } else { Write-Output "Pool '$($StoragePoolName)' already exists - skipping" } } catch { throw "error during creation of vm volume: $($PSItem.Exception.Message)" } } function New-VMVSwitch { <# .Description Creates a VM switch based on a network card with the Up state. 10Gbit NICs are preferred .Parameter Course_Shortcut Name of the VM Switch .Example # Creates a new VM Switch with the name IC New-VMVSwitch -Course_Shortcut 'IC' .NOTES there must be at least one nic with status 'up' #> param ( # Course Shortcut [Parameter(Mandatory = $false)] [string] $Course_Shortcut = "LAN" ) try { if ($null -eq (Get-VMSwitch -Name $Course_Shortcut -ErrorAction SilentlyContinue)) { $pNICs = Get-NetAdapter -Physical | Where-Object -Property Status -eq "UP" $10G_NICs = $pNICs | Where-Object -Property LinkSpeed -EQ "10 Gbps" $1G_NICs = $pNICs | Where-Object -Property LinkSpeed -EQ "1 Gbps" if ($10G_NICs) { $Selected_NIC = $10G_NICs[0] } elseif ($1G_NICs) { $Selected_NIC = $1G_NICs[0] } else { (Get-NetAdapter -Physical | Where-Object -Property Status -eq "UP")[0] } Write-Output "create vswitch '$($Course_Shortcut)'" New-VMSwitch -Name $Course_Shortcut -NetAdapterName $Selected_NIC.Name -AllowManagementOS $false | Out-Null Add-VMNetworkAdapter -ManagementOS -SwitchName $Course_Shortcut -Name "vNIC-$($Course_Shortcut)" Rename-NetAdapter -Name $Selected_NIC.Name -NewName "pNIC-$($Course_Shortcut)" } else { Write-Output "virtual vswitch '$($Course_Shortcut)' already exists - skipping" } } catch { throw "error during creation of virtual switch: $($PSItem.Exception.Message)" } } function Register-VM_in_CM { <# .Description Registers the VM Objects in ConfigMgr with its MAC address for Required Deployments .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Parameter CM_Siteserver_FQDN FQDN of ConfigMgr .Parameter CM_Credentials Credentials of a user that can create/edit/delete CMDevices and add them to a Collection. Should be able to start a collection update .Example # Registers the VMs from $VM_Config with $CM_Siteserver_FQDN Register-VM_in_CM -VM_Config_Obj $VM_Config -CM_Siteserver_FQDN $CM_Siteserver_FQDN -CM_Credentials $CM_Credentials .NOTES the VM_Config_Obj object, should be like ` `$VM_Config_Obj = @{} `$VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" } #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [hashtable] $VM_Config_Obj, [Parameter(Mandatory = $true)] [string] $CM_Siteserver_FQDN, [Parameter(Mandatory = $true)] [PSCredential] $CM_Credentials ) Write-Output "`nstart registration of vms in configmgr" Write-Output "------" Confirm-VMPresence -VM_Config_Obj $VM_Config_Obj try { Invoke-Command -ComputerName $CM_Siteserver_FQDN -Credential $CM_Credentials -ScriptBlock { $PSDriveName = "CM-PB2" $VM_Config_Obj = $using:VM_Config_Obj $CM_Collection_All_Systems_Name = "All Systems" # load configmgr ps module Import-Module -Name ConfigurationManager New-PSDrive -Name $PSDriveName -PSProvider "CMSite" -Root $using:CM_Siteserver_FQDN -Description "Primary site" | Out-Null Set-Location -Path "$($PSDriveName):\" # check destination collection existance foreach ($VM in $VM_Config_Obj.Keys | Sort-Object) { if ($null -eq (Get-CMCollection -Name $VM_Config_Obj.$VM.CM_Collection_Name)) { throw "collection '$($VM_Config_Obj.$VM.CM_Collection_Name)' does not existing" } } # cleanup before creation foreach ($VM in $VM_Config_Obj.Keys | Sort-Object) { $Temp_CMDevice = Get-CMDevice -Name $VM if ($null -ne $Temp_CMDevice) { $Collections = Get-CimInstance -Namespace root/Sms/site_PB1 -ClassName SMS_FullCollectionMembership -Filter "ResourceID = $($Temp_CMDevice.ResourceID)" if ($null -ne $Collections) { Write-Output "$($VM): removing existing computer collection membership rules configmgr - macaddress '$($VM_Config_Obj.$VM.MAC)'" $Collections | ForEach-Object { $MembershipRule = Get-CMCollectionDirectMembershipRule -CollectionId $PSItem.CollectionID | Where-Object -Property RuleName -EQ $CMDevice.Name Remove-CMDeviceCollectionDirectMembershipRule -CollectionId $PSItem.CollectionID -ResourceId $MembershipRule.ResourceID } } Write-Output "$($VM): removing existing computer info in configmgr - macaddress '$($VM_Config_Obj.$VM.MAC)'" $Temp_CMDevice | Remove-CMDevice -Force -Confirm:$false } } Start-Sleep -Seconds 5 # import device foreach ($VM in $VM_Config_Obj.Keys | Sort-Object) { Write-Output "$($VM): creating vm computer info in configmgr - macaddress '$($VM_Config_Obj.$VM.MAC)'" Import-CMComputerInformation -CollectionName $CM_Collection_All_Systems_Name -ComputerName $VM -MacAddress $VM_Config_Obj.$VM.MAC } # check vm collection membership foreach ($VM in $VM_Config_Obj.Keys | Sort-Object) { #region all systems collection $Device_Exists_In_AllDevices = $false $All_Devices_Counter = 0 Write-Output "$($VM): checking collection memberships" while ($Device_Exists_In_AllDevices -eq $false -and $All_Devices_Counter -lt 30) { $CMDevice = Get-CMDevice -Name $VM if ($CMDevice.Name -eq $VM) { $Device_Exists_In_AllDevices = $true } else { Start-Sleep -Seconds 5 $All_Devices_Counter++ } Write-Output "$($VM): device not found in collection '$($CM_Collection_All_Systems_Name)'" if ($All_Devices_Counter -eq 12) { Write-Output "$($VM): triggering membership update in Collection '$($CM_Collection_All_Systems_Name)'" Start-Sleep -Seconds (10 + (Get-Random -Maximum 50 -Minimum 10)) Get-CMCollection -Name $CM_Collection_All_Systems_Name | Invoke-CMCollectionUpdate } } if ($All_Devices_Counter -ge 30) { throw "$($VM): could not find in the collection '$($CM_Collection_All_Systems_Name)'" } else { Write-Output "$($VM): '$($CM_Collection_All_Systems_Name)' - found, continuing" } #endregion # create vm membership rule Add-CMDeviceCollectionDirectMembershipRule -CollectionName $VM_Config_Obj.$VM.CM_Collection_Name -ResourceID $CMDevice.ResourceID #region destination collection $Device_Exists_In_Specified_Collection = $false $Specified_Collection_Counter = 0 while ($Device_Exists_In_Specified_Collection -eq $false -and $Specified_Collection_Counter -lt 30) { $Collection_Direct_Members = Get-CMDeviceCollectionDirectMembershipRule -CollectionName $VM_Config_Obj.$VM.CM_Collection_Name | Where-Object RuleName -eq $VM if ($null -ne $Collection_Direct_Members) { $Device_Exists_In_Specified_Collection = $true } else { Start-Sleep -Seconds 5 $Specified_Collection_Counter++ } Write-Output "$($VM): device not found in collection '$($VM_Config_Obj.$VM.CM_Collection_Name)'" if ($Specified_Collection_Counter -eq 20) { Write-Output "$($VM): triggering membership update in Collection '$($VM_Config_Obj.$VM.CM_Collection_Name)'" Start-Sleep -Seconds (10 + (Get-Random -Maximum 50 -Minimum 10)) Get-CMCollection -Name $VM_Config_Obj.$VM.CM_Collection_Name | Invoke-CMCollectionUpdate } } if ($Specified_Collection_Counter -ge 30) { throw "$($VM): could not find in the collection '$($VM_Config_Obj.$VM.CM_Collection_Name)'" } else { Write-Output "$($VM): '$($VM_Config_Obj.$VM.CM_Collection_Name)' - found, continuing" } #endregion } Set-Location -Path $env:SystemDrive Remove-PSDrive -Name $PSDriveName } $SecondsToWait = 300 Write-Output "`nfinished registration of vms, now waiting $($SecondsToWait) seconds for the configmgr database updates and stabilization" Start-Sleep -Seconds $SecondsToWait } catch { throw "error during registration of device infos in configmgr: $($PSItem.Exception.Message)" } } function Start-VM_Deployment { <# .Description Starts VMs .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Example # Starts VMs, based on objects in $VM_Config Start-VM_Deployment -VM_Config_Obj $VM_Config .NOTES the VM_Config_Obj object, should be like ` `$VM_Config_Obj = @{} `$VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" } #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [hashtable] $VM_Config_Obj ) if ($null -eq $VM_Config_Obj) { throw "no VM_Config_Obj was supplied, please specify" } try { Write-Output "`nstarting vm deployments" Write-Output "------" foreach ($VM in $VM_Config_Obj.Keys | Sort-Object) { Write-Output "$($VM): starting deployment" Start-Sleep -Seconds 2 Start-VM -VMName $VM } } catch { throw "error while starting vms: $($PSItem.Exception.Message)" } } function Confirm-VM_Deployment { <# .Description Checks the ConfigMgr database to see if the deployment of VM objects is complete. .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Parameter CM_Siteserver_FQDN FQDN of ConfigMgr .Parameter CM_Credentials Credentials of a user that can create/edit/delete CMDevices and add them to a Collection. Should be able to start a collection update .Example # Checks the ConfigMgr database to see if the deployment of VM objects in $VM_Config is complete. Confirm-VM_Deployment -VM_Config_Obj $VM_Config -CM_Siteserver_FQDN $CM_Siteserver_FQDN -CM_Credentials $CM_Credentials .NOTES the VM_Config_Obj object, should be like ` `$VM_Config_Obj = @{} `$VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" } #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [hashtable] $VM_Config_Obj, [Parameter(Mandatory = $true)] [string] $CM_Siteserver_FQDN, [Parameter(Mandatory = $true)] [PSCredential] $CM_Credentials ) if ($null -eq $VM_Config_Obj) { throw "no VM_Config_Obj was supplied, please specify" } if ($null -eq $CM_Siteserver_FQDN) { throw "no CM_Siteserver_FQDN was supplied, please specify" } if ($null -eq $CM_Credentials) { throw "no CM_Credentials was supplied, please specify" } try { Write-Output "`nchecking vm os deployment status" Write-Output "------" Confirm-VMPresence -VM_Config_Obj $VM_Config_Obj Invoke-Command -ComputerName $CM_Siteserver_FQDN -Credential $CM_Credentials -ScriptBlock { $PSDriveName = "CM-PB2" Import-Module -Name ConfigurationManager New-PSDrive -Name $PSDriveName -PSProvider "CMSite" -Root $using:CM_Siteserver_FQDN -Description "Primary site" | Out-Null Set-Location -Path "$($PSDriveName):\" $Deployment_Finished = $false $Deployment_Check_Count = 0 $CM_Deployment_Running = @{} foreach ($VM in $using:VM_Config_Obj.keys | Sort-Object) { $CM_Deployment_Running.Add($VM, $true) } do { $Deployment_Check_Count++ $CM_All_Deployments = Get-CMDeploymentStatus | Get-CMDeploymentStatusDetails | ` Where-Object -FilterScript { $using:VM_Config_Obj.keys -contains $PSItem.Devicename } | ` Sort-Object -Property DeviceName Write-Output "timestamp : $(Get-Date -format "yyyy-MM-dd_HH.mm.ss") - $($Deployment_Check_Count)" if ($null -ne $CM_All_Deployments) { foreach ($VM_Deployment in $CM_All_Deployments) { if ($VM_Deployment.StatusDescription -notlike "*The task sequence manager successfully completed execution of the task sequence*") { Write-Output "$($VM_Deployment.DeviceName): still running - $($VM_Deployment.StatusDescription)" $CM_Deployment_Running.$($VM_Deployment.Devicename) = $true } else { Write-Output "$($VM_Deployment.Devicename): finished - $($VM_Deployment.StatusDescription)" $CM_Deployment_Running.$($VM_Deployment.Devicename) = $false } } } else { Write-Output "Waiting on Deployments" } Write-Output "------" if ($CM_Deployment_Running.Values -notcontains $true) { $Deployment_Finished = $true } else { Start-Sleep -Seconds 30 } } while (($Deployment_Finished -eq $false) -and $Deployment_Check_Count -lt 180 ) if ($Deployment_Check_Count -ge 200) { throw "deployment not finished after 100 mins" } Set-Location -Path $env:SystemDrive Remove-PSDrive -Name $PSDriveName } Write-Output "`nfinished vm deployments" Write-Output "------" } catch { throw "error while checking deployment status: $($PSItem.Exception.Message)" } } function New-VMs_Objectbased { <# .Description Creates vms based on objects .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Parameter Course_Shortcut Name of the VM Switch .Parameter VMDriveLetter Letter for the volume where the vms should be stored .Example # Creates vms based on $VM_Config and connects them to the vm switch $Course_Shortcut New-VMs_Objectbased -VM_Config $VM_Config -Course_Shortcut $Course_Shortcut .NOTES edits $VM_Config by updating the macaddress attribute of each vm object the VM_Config_Obj object, should be like ` `$VM_Config_Obj = @{} `$VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" } #> param ( [Parameter(Mandatory = $false)] [hashtable] $VM_Config_Obj, [Parameter(Mandatory = $false)] [string] $Course_Shortcut = 'LAN', [Parameter(Mandatory = $false)] [char] $VMDriveLetter = 'V' ) if ($null -eq $VM_Config_Obj) { throw "no VM_Config_Obj was supplied, please specify" } try { $VM_Base_Path = $VMDriveLetter + ":\VMs" Write-Output "`nstarting vm creation" Write-Output "------" foreach ($VM in $VM_Config_Obj.Keys | Sort-Object) { $VMVHDXPath = ($VM_Base_Path + "\" + $VM_Config_Obj.$VM.Name + "\" + $VM_Config_Obj.$VM.Name + ".vhdx") Write-Output "$($VM_Config_Obj.$VM.Name): creating vm" try { New-VHD -Path $VMVHDXPath -SizeBytes $VM_Config_Obj.$VM.DiskSize -Dynamic | Out-Null New-VM -Name $VM_Config_Obj.$VM.Name -MemoryStartupBytes $VM_Config_Obj.$VM.RAM -Path $VM_Base_Path -Generation 2 -VHDPath $VMVHDXPath -BootDevice NetworkAdapter -SwitchName $Course_Shortcut | Out-Null } catch { throw "$($VM_Config_Obj.$VM.Name): error during creation of vhdx or vm - $($PSItem.Exception.Message)" } try { Set-VMProcessor -VMName $VM_Config_Obj.$VM.Name -Count $VM_Config_Obj.$VM.CPU if ((Get-VM -Name $VM_Config_Obj.$VM.Name).DynamicMemoryEnabled) { Set-VM -Name $VM_Config_Obj.$VM.Name -StaticMemory } Set-VMKeyProtector -VMName $VM_Config_Obj.$VM.Name -NewLocalKeyProtector Enable-VMTPM -VMName $VM_Config_Obj.$VM.Name Set-VM -AutomaticStartAction Start -VMName $VM_Config_Obj.$VM.Name -AutomaticStartDelay 10 Set-VM -AutomaticStopAction ShutDown -VMName $VM_Config_Obj.$VM.Name Get-VMIntegrationService -VMName $VM_Config_Obj.$VM.Name | Where-Object -Property Enabled -EQ $false | Enable-VMIntegrationService } catch { throw "$($VM_Config_Obj.$VM.Name): error while setting properties - $($PSItem.Exception.Message)" } Start-VM -Name $VM_Config_Obj.$VM.Name Start-Sleep -Seconds 2 Stop-VM -Name $VM_Config_Obj.$VM.Name -Force -TurnOff Start-Sleep -Seconds 1 } foreach ($VM in $VM_Config_Obj.Keys | Sort-Object) { $VM_Config_Obj.$VM.MAC = (Get-VM -Name $VM_Config_Obj.$VM.Name | Get-VMNetworkAdapter).MacAddress Set-VMNetworkAdapter -VMName $VM_Config_Obj.$VM.Name -StaticMacAddress $VM_Config_Obj.$VM.MAC } Confirm-VMPresence -VM_Config_Obj $VM_Config_Obj } catch { throw "error during creation of vms: $($PSItem.Exception.Message)" } } function Test-VMConnection { <# .Description checks if a powershell direct connection to the vm can be established .Parameter VMId Id of the vm .Parameter LocalAdminCreds local admin credentials of the vm .Example # checks the powershell direct connection to VDC01 Test-VMConnection -VMId (Get-VM -Name VDC01).Id -LocalAdminCreds $VM_Credentials .NOTES #> [cmdletbinding()] param ( # VM object id [Parameter(Mandatory = $true)] [Guid] $VMId, # local admin credentials [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSCredential] $LocalAdminCreds ) $VM = Get-VM -Id $VMId try { Write-Output "------" if ($VM.State -eq "Off") { Write-Output "------" Write-Output "$($VM.Name): not running - starting" $VM | Start-VM -WarningAction SilentlyContinue } # Wait for the VM's heartbeat integration component to come up if it is enabled $HearbeatIC = (Get-VMIntegrationService -VM $VM | Where-Object Id -match "84EAAE65-2F2E-45F5-9BB5-0E857DC8EB47") if ($HearbeatIC -and ($HearbeatIC.Enabled -eq $true)) { $StartTime = Get-Date do { $WaitForMinitues = 5 $TimeElapsed = $(Get-Date) - $StartTime if ($($TimeElapsed).TotalMinutes -ge 5) { throw "$($VM.Name): integration components did not come up after $($WaitForMinitues) minutes" } Start-Sleep -sec 1 } until ($HearbeatIC.PrimaryStatusDescription -eq "OK") Write-Output "$($VM.Name): heartbeat IC connected" } do { $WaitForMinitues = 5 $TimeElapsed = $(Get-Date) - $StartTime Write-Output "$($VM.Name): testing connection" if ($($TimeElapsed).TotalMinutes -ge 5) { throw "$($VM.Name): could not connect to ps direct after $($WaitForMinitues) minutes" } Start-Sleep -sec 3 $PSReady = Invoke-Command -VMId $VMId -Credential $LocalAdminCreds -ErrorAction SilentlyContinue -ScriptBlock { $True } } until ($PSReady) } catch { throw "$($VM.Name): $($PSItem.Exception.Message)" } return $PSReady } function Confirm-VMPresence { <# .Description checks if vms are registered with the local hypervisor .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Example # checks if the vms in $VM_Config_Obj are registerd to the local hypervisor Confirm-VMPresence -VM_Config_Obj $VM_Config_Obj .NOTES the VM_Config_Obj object, should be like ` `$VM_Config_Obj = @{} `$VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" } #> param ( [Parameter(Mandatory = $false)] [hashtable] $VM_Config_Obj ) try { $VM_Config_Obj.Keys | ForEach-Object { if ($null -eq (Get-VM -Name $VM_Config_Obj.$PSItem.Name)) { throw "$($VM_Config_Obj.$PSItem.Name): could not be found" } } } catch { throw "$($PSItem.Exception.Message)" } } function Get-SQLISO { <# .Description downloads sql iso from microsoft .Parameter Outpath path where the iso is stored .Example # stores the iso to $Outpath Get-SQLISO -Outpath $Outpath .NOTES downloads the sql enterprise eval setup and with it the iso #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Outpath ) if ((Test-Path -Path $Outpath) -eq $false) { New-Item -Path $Outpath -ItemType Directory -Force | Out-Null } $Outpath = (Get-Item -Path $Outpath).FullName $SQLEvalSetupPath = "SQL2019-SSEI-Eval.exe" $SQLEvalSetupFullPath = "$($Outpath)\$($SQLEvalSetupPath)" # downloading eval setup try { if ((Test-Path -Path $SQLEvalSetupFullPath) -eq $true) { if ((Get-Item $SQLEvalSetupFullPath).LastWriteTime -gt (Get-Date).AddHours(-2)) { # juenger als zwei stunden # do nothing Write-Output "$($env:COMPUTERNAME): found eval setup at $($SQLEvalSetupFullPath) will use it" } else { Write-Output "$($env:COMPUTERNAME): found eval setup at $($SQLEvalSetupFullPath), removing the files because too old" Remove-Item -Path $SQLEvalSetupFullPath -Recurse -Force | Out-Null Write-Output "$($env:COMPUTERNAME): downloading eval setup" $URL = "https://download.microsoft.com/download/4/8/6/486005eb-7aa8-4128-aac0-6569782b37b0/SQL2019-SSEI-Eval.exe" New-Item -Path $Outpath -Force -ItemType Directory | Out-Null Invoke-WebRequest -UseBasicParsing -Uri $URL -OutFile $SQLEvalSetupFullPath } } else { Write-Output "$($env:COMPUTERNAME): downloading eval setup" $URL = "https://download.microsoft.com/download/4/8/6/486005eb-7aa8-4128-aac0-6569782b37b0/SQL2019-SSEI-Eval.exe" New-Item -Path $Outpath -Force -ItemType Directory | Out-Null Invoke-WebRequest -UseBasicParsing -Uri $URL -OutFile $SQLEvalSetupFullPath } } catch { throw "error downloading eval setup: $($PSItem.Exception.Message)" } # downloading iso try { $ISOPath = (Get-ChildItem -Path $Outpath | Where-Object -FilterScript { $PSItem.Name -like "SQL*.iso" }).FullName if ($null -ne $ISOPath -and (Test-Path -Path $ISOPath) -eq $true) { if ((Get-Item $ISOPath).LastWriteTime -gt (Get-Date).AddHours(-2)) { # juenger als zwei stunden # do nothing Write-Output "$($env:COMPUTERNAME): found sql iso at $($ISOPath) will use it" } else { Write-Output "$($env:COMPUTERNAME): found sql iso at $($ISOPath), removing the files because too old" Remove-Item -Path $ISOPath -Recurse -Force | Out-Null Write-Output "$($env:COMPUTERNAME): downloading sql iso with eval setup" $Arguments = "/ACTION=Download /MEDIAPATH=$($Outpath)\ /MEDIATYPE=ISO /LANGUAGE=en-US /QUIET" Start-Process $SQLEvalSetupFullPath -ArgumentList $Arguments -Wait -NoNewWindow } } else { Write-Output "$($env:COMPUTERNAME): downloading sql iso with eval setup" $Arguments = "/ACTION=Download /MEDIAPATH=$($Outpath)\ /MEDIATYPE=ISO /LANGUAGE=en-US /QUIET" Start-Process $SQLEvalSetupFullPath -ArgumentList $Arguments -Wait -NoNewWindow } } catch { throw "error downloading iso: $($PSItem.Exception.Message)" } Write-Output "$($env:COMPUTERNAME): finished download - check folder '$($Outpath)'" } function Initialize-SQLSetup { <# .Description prepares the sql installation files for i .Parameter Outpath path where the data should be cached .Example # stores the files to $TempFolderForSQL Initialize-SQLSetup -Outpath $TempFolderForSQL .NOTES downloads the sql install files and stores them extracted to the outpath #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Outpath ) if ((Test-Path -Path $Outpath) -eq $false) { New-Item -Path $Outpath -ItemType Directory -Force | Out-Null } $Outpath = (Get-Item -Path $Outpath).FullName # checks $SQLFileFolder = "$($Outpath)\SQLFiles\" if ((Test-Path -Path $SQLFileFolder) -eq $true ) { throw "$($env:COMPUTERNAME): folder '$($SQLFileFolder)' already exits, please cleanup or skip" } # downloading iso try { Get-SQLISO -Outpath $Outpath } catch { throw "error during download: $($PSItem.Exception.Message)" } # copying files try { if ((Test-Path -Path $SQLFileFolder) -eq $true) { if ((Get-Item $SQLFileFolder).LastWriteTime -gt (Get-Date).AddHours(-2)) { # juenger als zwei stunden # do nothing Write-Output "$($env:COMPUTERNAME): found setup files at $($SQLFileFolder) will use it" } else { Write-Output "$($env:COMPUTERNAME): found setup files at $($SQLFileFolder), removing the files because too old" Remove-Item -Path $SQLFileFolder -Recurse -Force | Out-Null Write-Output "$($env:COMPUTERNAME): mounting iso" $ISO = Get-ChildItem -Path $Outpath | Where-Object -FilterScript { $PSItem.Name -like "SQL*.iso" } $MountedISOs = Mount-DiskImage -PassThru -ImagePath $ISO.FullName $Volume = Get-Volume -DiskImage $MountedISOs Write-Output "$($env:COMPUTERNAME): file '$($ISO.FullName)' mounted to '$($Volume.DriveLetter):\'" Write-Output "$($env:COMPUTERNAME): copy files from iso to $($SQLFileFolder)" Copy-Item -Path "$($Volume.DriveLetter):\" -Destination $SQLFileFolder -Recurse -Force Write-Output "$($env:COMPUTERNAME): finished copy job" Write-Output "$($env:COMPUTERNAME): dismounting iso" Dismount-DiskImage $MountedISOs.ImagePath | Out-Null Write-Output "$($env:COMPUTERNAME): dismounting finished" } } else { Write-Output "$($env:COMPUTERNAME): mounting iso" $ISO = Get-ChildItem -Path $Outpath | Where-Object -FilterScript { $PSItem.Name -like "SQL*.iso" } $MountedISOs = Mount-DiskImage -PassThru -ImagePath $ISO.FullName $Volume = Get-Volume -DiskImage $MountedISOs Write-Output "$($env:COMPUTERNAME): file '$($ISO.FullName)' mounted to '$($Volume.DriveLetter):\'" Write-Output "$($env:COMPUTERNAME): copy files from iso to $($SQLFileFolder)" Copy-Item -Path "$($Volume.DriveLetter):\" -Destination $SQLFileFolder -Recurse -Force Write-Output "$($env:COMPUTERNAME): finished copy job" Write-Output "$($env:COMPUTERNAME): dismounting iso" Dismount-DiskImage $MountedISOs.ImagePath | Out-Null Write-Output "$($env:COMPUTERNAME): dismounting finished" } } catch { throw "error during mount or copy: $($PSItem.Exception.Message)" } } function Install-SQLADServiceAccount { <# .Description adds the local server to a group with permission to the serviec account and then installs it .Parameter SQLServiceAccount name of the group managed service account .Parameter GroupWithPermissions name of the group with permissions to retrieve the password of the group managed service account .Example # this installs a gMSA Install-SQLADServiceAccount -SQLServiceAccount $SQLEngine.Name -GroupWithPermissions $SQLEngine.GroupWithPermissions .NOTES - the server needs to be member of a domain - the group managed service account must exit - the user which runs this command must have permissions to add the local device to the group #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $SQLServiceAccount, [Parameter(Mandatory = $true)] [string] $GroupWithPermissions ) if ((Get-CimInstance -ClassName Win32_ComputerSystem).PartofDomain -eq $false) { throw "$($env:COMPUTERNAME): not a member of a domain" } Install-WindowsFeature -Name RSAT-AD-PowerShell | Out-Null if ($null -eq (Get-ADServiceAccount -Identity $SQLServiceAccount -ErrorAction SilentlyContinue)) { throw "service account '$($SQLServiceAccount)$' could not be found" } # adding device to group try { Write-Output "$($env:COMPUTERNAME): adding to group '$($GroupWithPermissions)'" $Self = Get-ADComputer -Identity $env:COMPUTERNAME Add-ADGroupMember -Identity $GroupWithPermissions -Members $Self } catch { throw "not able to add to group '$($GroupWithPermissions): $($PSItem.Exception.Message)" } # installing and testing service account try { Write-Output "$($env:COMPUTERNAME): installing '$($SQLServiceAccount)'" Start-Process -FilePath klist -ArgumentList "purge -lh 0 -li 0x3e7" -NoNewWindow Install-ADServiceAccount -Identity ($SQLServiceAccount + "$") if ((Test-ADServiceAccount -Identity ($SQLServiceAccount + "$")) -eq $false) { throw "service account '$($SQLServiceAccount)$' is not installed, please verify" } } catch { throw "could install '$($env:SQLServiceAccount)' - $($PSItem.Exception.Message)" } } function Install-SQLInstance { <# .Description adds the local server to a group with permission to the serviec account and then installs it .Parameter Name name of group managed service account .Parameter UseLocalAccount specify if local accounts are used .Parameter UseGmSA specify if group managed service accounts are used .Parameter EngineAccountName service account name for sql engine .Parameter AgentAccountName service account name for sql agent .Parameter UseMixedAuth when used, the instance will use sql and windows authentication .Parameter SAPWD sa pwd .Parameter INSTALLSQLDATADIR specifies the data directory for SQL Server data files .Parameter INSTANCEDIR specifies nondefault installation directory for instance-specific components .Parameter SQLBACKUPDIR backup file path for the sql database .Parameter SQLUSERDBDIR user file path for the sql database .Parameter SQLUSERDBLOGDIR user log file path for the sql database .Parameter SQLTEMPDBDIR temp file path for the sql database .Parameter SQLTEMPDBLOGDIR temp log file path for the sql database .Parameter Features features that should be installed, like 'SQLENGINE,FULLTEXT,CONN' .Parameter SQLSYSADMINACCOUNTS accounts that should be admin on that instance .Parameter SQLMinRAM minimum ram for instance .Parameter SQLMaxRAM maximum ram for instance .Example # this installs an sql instance with the name PB1 which uses gmsa with customized file paths Install-SQLInstance -Name "PB1" ` -Features "SQLENGINE" ` -SQLSYSADMINACCOUNTS ('"' + "IC\T1-CMAdmin" + '" "' + "IC\T1-CMAdmins" + '"') ` -SQLBACKUPDIR "S:\SQL\PB1\BACKUP\DATA" ` -SQLUSERDBDIR "S:\SQL\PB1\USERDATA\DATA" ` -SQLUSERDBLOGDIR "S:\SQL\PB1\USERLOG\LOG" ` -SQLTEMPDBDIR "S:\SQL\PB1\TEMPDATA\DATA" ` -SQLTEMPDBLOGDIR "S:\SQL\PB1\TEMPLOG\LOG" ` -UseGmSA ` -EngineAccountName $SQLEngine ` -AgentAccountName $SQLAgent .Example # this installs an sql instance with the name INSTANCE and local accounts to default file paths Install-SQLInstance -Name "INSTANCE" -UseLocalAccount .NOTES #> [CmdletBinding(DefaultParameterSetName = 'local')] param ( [Parameter(ParameterSetName = 'local', Mandatory = $false)] [Parameter(ParameterSetName = 'gmsa', Mandatory = $false)] [string] $SetupPath = ".\SQLFiles\setup.exe", [Parameter(ParameterSetName = 'local', Mandatory = $true)] [Parameter(ParameterSetName = 'gmsa', Mandatory = $true)] [string] $Name, [Parameter(ParameterSetName = 'local')] [switch] $UseLocalAccount, [Parameter(ParameterSetName = 'gmsa')] [switch] $UseGmSA, [Parameter(ParameterSetName = 'gmsa', Mandatory = $true)] [string] $EngineAccountName, [Parameter(ParameterSetName = 'gmsa', Mandatory = $true)] [string] $AgentAccountName, [Parameter(ParameterSetName = 'local')] [Parameter(ParameterSetName = 'gmsa')] [switch] $UseMixedAuth, [Parameter(ParameterSetName = 'local')] [Parameter(ParameterSetName = 'gmsa')] [string] $SAPWD, [Parameter(ParameterSetName = 'local', Mandatory = $false)] [Parameter(ParameterSetName = 'gmsa', Mandatory = $false)] [string] $INSTALLSQLDATADIR, [Parameter(ParameterSetName = 'local', Mandatory = $false)] [Parameter(ParameterSetName = 'gmsa', Mandatory = $false)] [string] $INSTANCEDIR, [Parameter(ParameterSetName = 'local')] [Parameter(ParameterSetName = 'gmsa')] [string] $SQLBACKUPDIR, [Parameter(ParameterSetName = 'local')] [Parameter(ParameterSetName = 'gmsa')] [string] $SQLUSERDBDIR, [Parameter(ParameterSetName = 'local')] [Parameter(ParameterSetName = 'gmsa')] [string] $SQLUSERDBLOGDIR, [Parameter(ParameterSetName = 'local')] [Parameter(ParameterSetName = 'gmsa')] [string] $SQLTEMPDBDIR, [Parameter(ParameterSetName = 'local')] [Parameter(ParameterSetName = 'gmsa')] [string] $SQLTEMPDBLOGDIR, [Parameter(ParameterSetName = 'local', Mandatory = $false)] [Parameter(ParameterSetName = 'gmsa', Mandatory = $false)] [string] $Features, [Parameter(ParameterSetName = 'local')] [Parameter(ParameterSetName = 'gmsa')] [string] $SQLSYSADMINACCOUNTS, [Parameter(ParameterSetName = 'local')] [Parameter(ParameterSetName = 'gmsa')] [string] $SQLMinRAM, [Parameter(ParameterSetName = 'local')] [Parameter(ParameterSetName = 'gmsa')] [string] $SQLMaxRAM ) # define arguments if ($UseLocalAccount -eq $false -and $UseGmSA -eq $false) { throw "you have to specify 'UseLocalAccount' or 'UseGmSA'" } if ($UseMixedAuth -eq $false -and $null -eq $SAPWD) { throw "you have to specify 'SAPWD' when 'UseMixedAuth' is used" } if ($uselocalaccount) { $AGTSVCACCOUNT = 'NT Service\SQLAgent$' + $Name $SQLSVCACCOUNT = 'NT Service\MSSQL$' + $Name } elseif ($UseGmSA) { $AGTSVCACCOUNT = $env:USERDOMAIN + "\" + (Get-ADServiceAccount -Identity $AgentAccountName).SamAccountName $SQLSVCACCOUNT = $env:USERDOMAIN + "\" + (Get-ADServiceAccount -Identity $EngineAccountName).SamAccountName } $SQLTELSVCACCT = 'NT Service\SQLTELEMETRY$' + $Name if ($Features -eq "") { $Features = "SQLENGINE" } if ($null -eq $SQLBACKUPDIR) { $SQLBACKUPDIR = $INSTALLSQLDATADIR + "\BACKUP\DATA" } if ($null -eq $SQLUSERDBDIR) { $SQLUSERDBDIR = $INSTALLSQLDATADIR + "\USER\DATA" } if ($null -eq $SQLUSERDBLOGDIR) { $SQLUSERDBLOGDIR = $INSTALLSQLDATADIR + "\USERLOG\LOG" } if ($null -eq $SQLTEMPDBDIR) { $SQLTEMPDBDIR = $INSTALLSQLDATADIR + "\TEMP\DATA" } if ($null -eq $SQLTEMPDBLOGDIR) { $SQLTEMPDBLOGDIR = $INSTALLSQLDATADIR + "\TEMPLOG\LOG" } if ($null -eq $SQLSYSADMINACCOUNTS) { $SQLSYSADMINACCOUNTS = ('"' + "$($env:COMPUTERNAME)\Administrator" + '"') } # check dependencies try { if($UseGmSA){ if($env:USERDOMAIN -eq $env:COMPUTERNAME){ throw "you are logged in with a local user, domain user required" } if ((Test-ADServiceAccount -Identity "$($EngineAccountName)$") -eq $false) { throw "gmsa '$($EngineAccountName)' not installed" } if ((Test-ADServiceAccount -Identity "$($AgentAccountName)$") -eq $false) { throw "gmsa '$($AgentAccountName)' not installed" } } $InstalledInstances = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server' -ErrorAction SilentlyContinue).InstalledInstances if ($InstalledInstances -contains $Name) { throw "SQL instance is already installed '$($Name)'" } } catch { throw "error during prerequesits check : $($Psitem.Exception.Message)" } # start installation try { Write-Output "$($env:COMPUTERNAME): starting install of '$($Name)'" $Arguments = @( '/IACCEPTSQLSERVERLICENSETERMS="True"' '/IACCEPTPYTHONLICENSETERMS="False"' '/ACTION="Install"' '/ENU="True"' '/IACCEPTROPENLICENSETERMS="False"' '/SUPPRESSPRIVACYSTATEMENTNOTICE="False"' '/QUIET="True"' '/QUIETSIMPLE="False"' '/UpdateEnabled="True"' '/USEMICROSOFTUPDATE="False"' '/SUPPRESSPAIDEDITIONNOTICE="False"' '/UpdateSource="MU"' ('/FEATURES=' + $Features) ('/INSTANCENAME="' + $Name + '"') '/INSTALLSHAREDDIR="C:\Program Files\Microsoft SQL Server"' '/INSTALLSHAREDWOWDIR="C:\Program Files (x86)\Microsoft SQL Server"' ('/INSTANCEID="' + $Name + '"') ('/SQLTELSVCACCT="' + $SQLTELSVCACCT + '"') '/SQLTELSVCSTARTUPTYPE="Automatic"' ('/AGTSVCACCOUNT="' + $AGTSVCACCOUNT + '"') '/AGTSVCSTARTUPTYPE="Automatic"' '/SQLSVCSTARTUPTYPE="Automatic"' '/SQLCOLLATION="SQL_Latin1_General_CP1_CI_AS"' ('/SQLSVCACCOUNT="' + $SQLSVCACCOUNT + '"') ('/SQLSYSADMINACCOUNTS=' + $SQLSYSADMINACCOUNTS) ('/SQLBACKUPDIR="' + $SQLBACKUPDIR + '"') ('/SQLUSERDBDIR="' + $SQLUSERDBDIR + '"') ('/SQLUSERDBLOGDIR="' + $SQLUSERDBLOGDIR + '"') ('/SQLTEMPDBDIR="' + $SQLTEMPDBDIR + '"') ('/SQLTEMPDBLOGDIR="' + $SQLTEMPDBLOGDIR + '"') '/TCPENABLED="1"' '/NPENABLED="0"' '/BROWSERSVCSTARTUPTYPE="Automatic"' ) if ($UseMixedAuth -eq $true){ $SAArguments = @( '/SECURITYMODE="SQL"' '/SAPWD="' + $SAPWD + '"' ) $Arguments = $Arguments + $SAArguments } if ($INSTALLSQLDATADIR -eq $true){ $INSTALLSQLDATADIR_Arguments = @( ('/INSTALLSQLDATADIR="' + $INSTALLSQLDATADIR + '"') ) $Arguments = $Arguments + $INSTALLSQLDATADIR_Arguments } if ($INSTANCEDIR -eq $true){ $INSTANCEDIR_Arguments = @( ('/INSTANCEDIR="' + $INSTANCEDIR + '"') ) $Arguments = $Arguments + $INSTANCEDIR_Arguments } if ($SQLMinRAM -eq $true){ $MinRAMArguments = @( ('/SQLMINMEMORY="' + $SQLMinRAM + '"') ) $Arguments = $Arguments + $MinRAMArguments } if ($SQLMaxRAM -eq $true){ $MaxRAMArguments = @( ('/SQLMAXMEMORY="' + $SQLMaxRAM + '"') ) $Arguments = $Arguments + $MaxRAMArguments } $Process = Start-Process $SetupPath -ArgumentList $Arguments -Wait -NoNewWindow -PassThru if ($Process.ExitCode -ne 0) { throw "check logs of sql setup - $($env:ProgramFiles)\Microsoft SQL Server\*\Setup Bootstrap\Log" } } catch { throw "error during installation of sql instance '$($Name): $($Psitem.EXception.Message)" } # service customization try { if($UseGmSA){ Write-Output "$($env:COMPUTERNAME): configuring SQLAgent`$$($Name) service to autostart delayed" $Agent = (Get-Service -Name "SQLAgent`$$($Name)").Name Start-Process "sc" -ArgumentList ('config "' + $($Agent) + '" start=delayed-auto') -NoNewWindow Write-Output "$($env:COMPUTERNAME): configuring MSSQL`$$($Name) service to autostart delayed" $Engine = (Get-Service -Name "MSSQL`$$($Name)").Name Start-Process "sc" -ArgumentList ('config "' + $($Engine) + '" start=delayed-auto') -NoNewWindow } } catch { throw "error during service customization : $($Psitem.EXception.Message)" } Write-Output "$($env:COMPUTERNAME): finished install of '$($Name)'" } function Install-SQLCU { <# .Description installs sql 2019 latest cu to all local instances .Example # this installes sql 2019 latest cu to all instances Install-SQLCU .NOTES #> try { $TempFolderForSQL = "C:\ProgramData\NTS\SQL\CU" $SQLCUFilePath = "$($TempFolderForSQL)\SQL2019-latest-cu.exe" $SQLCUUnpackedFilePath = "$($TempFolderForSQL)\SQL2019-latest-cu\" if ((Test-Path -Path $TempFolderForSQL) -eq $false) { New-Item -Path $TempFolderForSQL -ItemType Directory -Force | Out-Null } # link to latest cu of sql 2019 https://www.microsoft.com/en-us/download/confirmation.aspx?id=100809 $Content = Invoke-WebRequest -UseBasicParsing -Uri "https://www.microsoft.com/en-us/download/confirmation.aspx?id=100809" $UpdateLink = ($Content.Links | Where-Object -FilterScript { $PSItem.href -like "*download.microsoft.com*" -and $PSItem.outerHTML -like "*download manually*" }).href Write-Output "$($env:COMPUTERNAME): downloading sql cu" Invoke-WebRequest -UseBasicParsing -Uri $UpdateLink -OutFile $SQLCUFilePath Write-Output "$($env:COMPUTERNAME): unpacking sql cu" $Arguments = "/X:$($SQLCUUnpackedFilePath)" $Process = Start-Process $SQLCUFilePath -ArgumentList $Arguments -Wait -NoNewWindow -PassThru if ($Process.ExitCode -ne 0) { throw "there was an error unpacking cu files" } Write-Output "$($env:COMPUTERNAME): installing sql cu" $Arguments = "/ACTION=Patch /ALLINSTANCES /QUIET /IACCEPTSQLSERVERLICENSETERMS /ENU" $Process = Start-Process ($SQLCUUnpackedFilePath + "setup.exe") -ArgumentList $Arguments -Wait -NoNewWindow -PassThru if ($Process.ExitCode -ne 0) { throw "there was an error installing sql cu" } Write-Output "$($env:COMPUTERNAME): removing temp files" try { Remove-Item -Path $TempFolderForSQL -Recurse -Force } catch { throw "error while cleanup - $($PSItem.Exception.Message)" } } catch { throw "$($env:COMPUTERNAME): error sql cu installation - $($PSItem.Exception.Message) - see logs at C:\Program Files\Microsoft SQL Server\*\Setup Bootstrap\Log\" } } function Add-VMDisk { <# .Description add a virtual disk to a vm .Parameter VMName name of the vm .Parameter VHDXPath where should the vhdx file be stored .Parameter VHDXSize size in byte of the disk .Example # this will add a disk to the vm and store ist at the file location from the vm Add-VMDisk -VMName $VM.Name -VHDXPath ($VM.ConfigurationLocation + "\" + $VM.Name + "-2.vhdx") -VHDXSize $DMP_ContentLibDisk_Size .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $VMName, [Parameter(Mandatory = $true)] [string] $VHDXPath, [Parameter(Mandatory = $false)] [string] $VHDXSize = 80GB ) try { Write-Host "$($VMName): creating disk '$($VHDXPath)'" New-VHD -Path $VHDXPath -SizeBytes $VHDXSize -Dynamic | Out-Null Add-VMHardDiskDrive -VMName $VMName -Path $VHDXPath } catch { throw "$($PSItem.Exception.Message)" } } function Set-VMIPConfig { <# .Description configures the network interface of a vm .Parameter VMName name of the vm .Parameter VMCredential local admin credentials of the vm .Parameter IPAddress ip address that should be assigned .Parameter NetPrefix subnet prefix, aka 24 or 16 .Parameter DefaultGateway gateway of the subnet .Parameter DNSAddresses dns server ip addresses .Example # this will configure the ip interface Set-VMIPConfig -VMName $DMP_VM.Name -VMCredential $VMCredential-IPAddress "192.168.1.21" -NetPrefix $NetPrefix -DefaultGateway $DefaultGateway -DNSAddresses $DNSAddresses .NOTES always uses the first nic founc on the system #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $VMName, [Parameter(Mandatory = $true)] [pscredential] $VMCredential, [Parameter(Mandatory = $true)] [string] $IPAddress, [Parameter(Mandatory = $true)] [int] $NetPrefix, [Parameter(Mandatory = $true)] [string] $DefaultGateway, [Parameter(Mandatory = $true)] [string[]] $DNSAddresses ) try { Write-Host "$($VMName): configuring network" Invoke-Command -VMName $VMName -Credential $VMCredential -ScriptBlock { $InterfaceObject = (Get-NetAdapter)[0] Write-Host "$($env:COMPUTERNAME): nic with mac '$($InterfaceObject.MacAddress)' was selected" If (($InterfaceObject | Get-NetIPConfiguration).IPv4Address.IPAddress) { $InterfaceObject | Remove-NetIPAddress -AddressFamily "IPv4" -Confirm:$false } If (($InterfaceObject | Get-NetIPConfiguration).Ipv4DefaultGateway) { $InterfaceObject | Remove-NetRoute -AddressFamily "IPv4" -Confirm:$false } Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\$($InterfaceObject.InterfaceGuid)" -Name EnableDHCP -Value 0 Start-Sleep -Seconds 2 Write-Host "$($env:COMPUTERNAME): nic with mac '$($InterfaceObject.MacAddress)' will have ip '$($using:IPAddress)'" $InterfaceObject | New-NetIPAddress -IPAddress $using:IPAddress -AddressFamily "IPv4" -PrefixLength $using:NetPrefix -DefaultGateway $using:DefaultGateway | Out-Null $InterfaceObject | Set-DnsClientServerAddress -ServerAddresses $using:DNSAddresses } } catch { throw "$($VMName): error setting ip interface - $($PSItem.Exception.Message)" } } function Add-VMToDomain { <# .Description takes the vm into a domain .Parameter VMName name of the vm .Parameter VMCredential local admin credentials of the vm .Parameter DomainName name of the domain where the vm should be joined .Parameter DomainCredential domain credentials with the permission to join devices .Parameter OUPath ou path in the domain where the vm should be organized .Parameter NoReboot when used, the vm will not reboot after join .Example # this will join the vm to the specified domain and OU Add-VMToDomain -VMName $SiteServer_VM.Name -VMCredential $VMCredential -DomainName $DomainName -DomainCredential $Domain_Credentials -OUPath "OU=Servers,OU=CM,OU=TIER-1,OU=ESAE,DC=INTUNE-CENTER,DC=DE" .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $VMName, [Parameter(Mandatory = $true)] [pscredential] $VMCredential, [Parameter(Mandatory = $true)] [string] $DomainName, [Parameter(Mandatory = $true)] [pscredential] $DomainCredential, [Parameter(Mandatory = $false)] [string] $OUPath, [Parameter(Mandatory = $false)] [switch] $NoReboot ) try { Confirm-VMState -VMObject (Get-VM -Name $VMName) -VMCredential $VMCredential Write-Output "$($VMName): joining domain '$($DomainName)'" Invoke-Command -VMName $VMName -Credential $VMCredential -ScriptBlock { Start-Sleep -Seconds 1 if ($NoReboot -eq $true) { if ($null -ne $OUPath) { Add-Computer -Credential $using:DomainCredential -DomainName $using:DomainName -OUPath $using:OUPath -WarningAction SilentlyContinue } else { Add-Computer -Credential $using:DomainCredential -DomainName $using:DomainName -WarningAction SilentlyContinue } } else { if ($null -ne $OUPath) { Add-Computer -Credential $using:DomainCredential -DomainName $using:DomainName -OUPath $using:OUPath -Restart -WarningAction SilentlyContinue } else { Add-Computer -Credential $using:DomainCredential -DomainName $using:DomainName -Restart -WarningAction SilentlyContinue } } } if ($NoReboot -eq $true) { Write-Output "$($VMName): domainjoin succesful - vm reboot required" } else { Write-Output "$($VMName): domainjoin succesful - vm will do reboot" } } catch { throw "$($VMName): error joining to domain '$($DomainName)' - $($PSItem.Exception.Message)" } } function Confirm-VMState { <# .Description checks if the vm is running and starts it if necessary then checks the connection to the vm via powershell direct .Parameter VMObject name of the vm .Parameter VMCredential local admin credentials of the vm .Example # this will check the specified vm Confirm-VMState -VMObject $VM -VMCredential $VMCred .NOTES VMObject should be like this: $VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" #> param ( # VM Objects [Parameter(Mandatory = $true)] [System.Object] $VMObject, [Parameter(Mandatory = $true)] [PSCredential] $VMCredential ) try { if ($VMObject.State -ne "Running") { Start-VM -VM $VMObject Start-Sleep -Seconds 10 } if (Test-VMConnection -VMId $VMObject.Id -LocalAdminCreds $VMCredential) { # Write-Output "$($VMObject.Name): connected succesfully" } else { throw "$($VMObject.Name): error while connecting with ps direct - $($PSItem.Exception.Message)" } } catch { throw "$($PSItem.Exception.Message)" } } function Install-NLASvcFixForDCs { <# .Description checks if dns is specified as dependy for service nlasvc and if its not the case it will add it. .Example # this will service dns as dependency for the service nlasvc Install-NLASvcFixForDCs .NOTES #> try { $RegKey = "HKLM:\SYSTEM\CurrentControlSet\Services\NlaSvc" $RegKeyValue = "DependOnService" $ServiceName = "DNS" Write-Output "$($env:COMPUTERNAME): adding service $($ServiceName) as dependency for service NlaSvc" $NLASvcDependencies = (Get-ItemProperty -Path $RegKey -Name $RegKeyValue).$RegKeyValue if($NLASvcDependencies -notcontains $ServiceName) { $FixedNLASvcDependencies = $NLASvcDependencies + $ServiceName Set-ItemProperty -Path $RegKey -Name $RegKeyValue -Value $FixedNLASvcDependencies Write-Output "$($env:COMPUTERNAME): NlaSvc fix applied" } else { Write-Output "$($env:COMPUTERNAME): NlaSvc fix already applied" } } catch { throw $PSItem.Exception.Message } } function New-EASEOUStructure { <# .Description this will create the esae structure with OUs, User, Groups and Group Managed Service Accounts .Example # creates the ease structure on the local domain controller New-EASEOUStructure .NOTES must be executed on a domain controller #> if ((Get-CimInstance -ClassName Win32_OperatingSystem).ProductType -ne 2) { throw "$($env:COMPUTERNAME): this is not a domain controller, can only be used on domain controllers" } Import-Module Activedirectory $Domain = Get-ADDomain Write-Output "$($env:COMPUTERNAME): apply esae structure on $($Domain.Forest)" #region ous try { Write-Output "$($env:COMPUTERNAME): creating ous" $TopLevelOU = 'ESAE' New-ADOrganizationalUnit -Name $TopLevelOU -Path $Domain.DistinguishedName # ESAE $ESAEPath = "OU=$TopLevelOU,$($Domain.DistinguishedName)" $ESAEOUs = 'Groups', 'PAW', 'Tier 0', 'Tier 1', 'Tier 2' $ESAEOUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $ESAEPath } # ESAE\PAW $PAWPath = "OU=PAW,$ESAEPath" $PAWOUs = 'Accounts', 'Devices', 'Groups' $PAWOUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $PAWPath } # ESAE\Tier0 $Tier0Path = "OU=Tier 0,$ESAEPath" $Tier0OUs = 'AD', 'PKI', 'SQL', 'ADFS', 'ADSync' $Tier0OUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $Tier0Path } # ESAE\Tier1 $Tier1Path = "OU=Tier 1,$ESAEPath" $Tier1OUs = 'Exchange', 'ConfigMgr', 'SQL' $Tier1OUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $Tier1Path } # ESAE\Tier2 $Tier2Path = "OU=Tier 2,$ESAEPath" $Tier2OUs = 'Accounts', 'Devices', 'Groups' $Tier2OUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $Tier2Path } # ESAE\Tier0\AD $Tier0ADPath = "OU=AD,$Tier0Path" $Tier0ADOUs = 'Accounts', 'Groups' $Tier0ADOUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $Tier0ADPath } # ESAE\Tier0\PKI $Tier0PKIPath = "OU=PKI,$Tier0Path" $Tier0PKIOUs = 'Accounts', 'Groups', 'Servers' $Tier0PKIOUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $Tier0PKIPath } # ESAE\Tier0\SQL $Tier0SQLPath = "OU=SQL,$Tier0Path" $Tier0SQLOUs = 'Accounts', 'Groups', 'Servers' $Tier0SQLOUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $Tier0SQLPath } # ESAE\Tier0\ADFS $Tier0ADFSPath = "OU=ADFS,$Tier0Path" $Tier0ADFSOUs = 'Accounts', 'Groups', 'Servers' $Tier0ADFSOUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $Tier0ADFSPath } # ESAE\Tier0\ADSync $Tier0ADSyncPath = "OU=ADSync,$Tier0Path" $Tier0ADSyncOUs = 'Accounts', 'Groups', 'Servers' $Tier0ADSyncOUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $Tier0ADSyncPath } # ESAE\Tier1\Exchange $Tier1ExchangePath = "OU=Exchange,$Tier1Path" $Tier1ExchangeOUs = 'Accounts', 'Groups', 'Servers' $Tier1ExchangeOUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $Tier1ExchangePath } # ESAE\Tier1\ConfigMgr $Tier1ConfigMgrPath = "OU=ConfigMgr,$Tier1Path" $Tier1ConfigMgrOUs = 'Accounts', 'Groups', 'Servers' $Tier1ConfigMgrOUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $Tier1ConfigMgrPath } # ESAE\Tier1\SQL $Tier1SQLPath = "OU=SQL,$Tier1Path" $Tier1SQLOUs = 'Accounts', 'Groups', 'Servers' $Tier1SQLOUs | ForEach-Object { New-ADOrganizationalUnit -Name $PSItem -Path $Tier1SQLPath } } catch { throw "error creating ous: $($PSItem.Exception.Message)" } #endregion #region groups try { Write-Output "$($env:COMPUTERNAME): creating groups" # PAW $GroupNames = 'T0-Allowed', 'T1-Allowed', 'T2-Allowed', 'T0-Denied', 'T1-Denied', 'T2-Denied' $GroupNames | ForEach-Object { New-ADGroup -Name $PSItem -SamAccountName $PSItem -GroupCategory Security -GroupScope Universal -DisplayName $PSItem -Path "OU=Groups,$ESAEPath" } $PAWGroupNames = 'PAW-Devices', 'PAW-Maintenance', 'PAW-Users' $PAWGroupNames | ForEach-Object { New-ADGroup -Name $PSItem -SamAccountName $PSItem -GroupCategory Security -GroupScope Universal -DisplayName $PSItem -Path "OU=Groups,$PAWPath" } # T0 $Tier0ADGroupNames = 'T0-CloudAdmins', 'T0-DomAdmins', 'T0-EntAdmins', 'T0-RODCAdmins', 'T0-SchAdmins' $Tier0ADGroupNames | ForEach-Object { New-ADGroup -Name $PSItem -SamAccountName $PSItem -GroupCategory Security -GroupScope Universal -DisplayName $PSItem -Path "OU=Groups,$Tier0ADPath" } $Tier0PKIGroupNames = 'T0-PKIAdmins', 'T0-PKISCAgents' $Tier0PKIGroupNames | ForEach-Object { New-ADGroup -Name $PSItem -SamAccountName $PSItem -GroupCategory Security -GroupScope Universal -DisplayName $PSItem -Path "OU=Groups,$Tier0PKIPath" } $Tier0SQLGroupNames = 'T0-SQLAdmins', 'T0-SQLServers' $Tier0SQLGroupNames | ForEach-Object { New-ADGroup -Name $PSItem -SamAccountName $PSItem -GroupCategory Security -GroupScope Universal -DisplayName $PSItem -Path "OU=Groups,$Tier0SQLPath" } $Tier0ADFSGroupNames = 'T0-ADFSAdmins', 'T0-ADFSServers', 'T0-ADFSSQLServers' $Tier0ADFSGroupNames | ForEach-Object { New-ADGroup -Name $PSItem -SamAccountName $PSItem -GroupCategory Security -GroupScope Universal -DisplayName $PSItem -Path "OU=Groups,$Tier0ADFSPath" } $Tier0ADSyncGroupNames = 'T0-ADSyncAdmins', 'T0-ADSyncServers', 'T0-ADSyncSQLServers' $Tier0ADSyncGroupNames | ForEach-Object { New-ADGroup -Name $PSItem -SamAccountName $PSItem -GroupCategory Security -GroupScope Universal -DisplayName $PSItem -Path "OU=Groups,$Tier0ADSyncPath" } # T1 $Tier1ExchangeGroupNames = 'T1-ExAdmins', 'T1-ExAllowed', 'T1-ExMaintenance', 'T1-ExMBXAdmins', 'T1-ExOrgAdmins' $Tier1ExchangeGroupNames | ForEach-Object { New-ADGroup -Name $PSItem -SamAccountName $PSItem -GroupCategory Security -GroupScope Universal -DisplayName $PSItem -Path "OU=Groups,$Tier1ExchangePath" } $Tier1CMGroupNames = 'T1-CMAdmins', 'T1-CMServers', 'T1-CMSQLServers' $Tier1CMGroupNames | ForEach-Object { New-ADGroup -Name $PSItem -SamAccountName $PSItem -GroupCategory Security -GroupScope Universal -DisplayName $PSItem -Path "OU=Groups,$Tier1ConfigMgrPath" } $Tier1SQLGroupNames = 'T1-SQLAdmins', 'T1-SQLServers' $Tier1SQLGroupNames | ForEach-Object { New-ADGroup -Name $PSItem -SamAccountName $PSItem -GroupCategory Security -GroupScope Universal -DisplayName $PSItem -Path "OU=Groups,$Tier1SQLPath" } # T2 $Tier2GroupNames = 'T2-HelpDesk' $Tier2GroupNames | ForEach-Object { New-ADGroup -Name $PSItem -SamAccountName $PSItem -GroupCategory Security -GroupScope Universal -DisplayName $PSItem -Path "OU=Groups,$Tier2Path" } } catch { throw "error creating groups: $($PSItem.Exception.Message)" } #endregion #region users try { Write-Output "$($env:COMPUTERNAME): creating users" $Password = ConvertTo-SecureString -String 'C0mplex' -AsPlainText -Force # PAW $PAWUserNames = 'PAWMan1' $PAWUserNames | ForEach-Object { New-ADUser -Name $PSItem -Path "OU=Accounts,$PAWPath" -SamAccountName $PSItem -UserPrincipalName "$PSItem@$($Domain.Forest)" -GivenName $PSItem -DisplayName $PSItem -AccountPassword $Password -Enabled $true -PasswordNeverExpires $true } # T0 $Tier0ADUserNames = 'T0-DomAdmin1', 'T0-EntAdmin1' $Tier0ADUserNames | ForEach-Object { New-ADUser -Name $PSItem -Path "OU=Accounts,$Tier0ADPath" -SamAccountName $PSItem -UserPrincipalName "$PSItem@$($Domain.Forest)" -GivenName $PSItem -DisplayName $PSItem -AccountPassword $Password -Enabled $true -PasswordNeverExpires $true } $Tier0PKIUserNames = 'T0-PKIAdmin1', 'T0-PKISCA1' $Tier0PKIUserNames | ForEach-Object { New-ADUser -Name $PSItem -Path "OU=Accounts,$Tier0PKIPath" -SamAccountName $PSItem -UserPrincipalName "$PSItem@$($Domain.Forest)" -GivenName $PSItem -DisplayName $PSItem -AccountPassword $Password -Enabled $true -PasswordNeverExpires $true } $Tier0SQLUserNames = 'T0-SQLAdmin1', 'T0-SQLAdmin2' $Tier0SQLUserNames | ForEach-Object { New-ADUser -Name $PSItem -Path "OU=Accounts,$Tier0SQLPath" -SamAccountName $PSItem -UserPrincipalName "$PSItem@$($Domain.Forest)" -GivenName $PSItem -DisplayName $PSItem -AccountPassword $Password -Enabled $true -PasswordNeverExpires $true } $Tier0ADFSUserNames = 'T0-ADFSAdmin1', 'T0-ADFSAdmin2' $Tier0ADFSUserNames | ForEach-Object { New-ADUser -Name $PSItem -Path "OU=Accounts,$Tier0ADFSPath" -SamAccountName $PSItem -UserPrincipalName "$PSItem@$($Domain.Forest)" -GivenName $PSItem -DisplayName $PSItem -AccountPassword $Password -Enabled $true -PasswordNeverExpires $true } $Tier0ADSyncUserNames = 'T0-ADSyncAdmin1', 'T0-ADSyncAdmin2' $Tier0ADSyncUserNames | ForEach-Object { New-ADUser -Name $PSItem -Path "OU=Accounts,$Tier0ADSyncPath" -SamAccountName $PSItem -UserPrincipalName "$PSItem@$($Domain.Forest)" -GivenName $PSItem -DisplayName $PSItem -AccountPassword $Password -Enabled $true -PasswordNeverExpires $true } # T1 $Tier1ExchangeUserNames = 'T1-ExAdmin1', 'T1-ExAdmin2' $Tier1ExchangeUserNames | ForEach-Object { New-ADUser -Name $PSItem -Path "OU=Accounts,$Tier1ExchangePath" -SamAccountName $PSItem -UserPrincipalName "$PSItem@$($Domain.Forest)" -GivenName $PSItem -DisplayName $PSItem -AccountPassword $Password -Enabled $true -PasswordNeverExpires $true } $Tier1CMUserNames = 'T1-CMAdmin1', 'T1-CMAdmin2' $Tier1CMUserNames | ForEach-Object { New-ADUser -Name $PSItem -Path "OU=Accounts,$Tier1ConfigMgrPath" -SamAccountName $PSItem -UserPrincipalName "$PSItem@$($Domain.Forest)" -GivenName $PSItem -DisplayName $PSItem -AccountPassword $Password -Enabled $true -PasswordNeverExpires $true } $Tier1SQLUserNames = 'T1-SQLAdmin1', 'T1-SQLAdmin2' $Tier1SQLUserNames | ForEach-Object { New-ADUser -Name $PSItem -Path "OU=Accounts,$Tier1SQLPath" -SamAccountName $PSItem -UserPrincipalName "$PSItem@$($Domain.Forest)" -GivenName $PSItem -DisplayName $PSItem -AccountPassword $Password -Enabled $true -PasswordNeverExpires $true } # T2 $Tier2UserNames = 'T2-HelpDesk1', 'T2-HelpDesk2' $Tier2UserNames | ForEach-Object { New-ADUser -Name $PSItem -Path "OU=Accounts,$Tier2Path" -SamAccountName $PSItem -UserPrincipalName "$PSItem@$($Domain.Forest)" -GivenName $PSItem -DisplayName $PSItem -AccountPassword $Password -Enabled $true -PasswordNeverExpires $true } } catch { throw "error creating users: $($PSItem.Exception.Message)" } #endregion #region gmsa try { Write-Output "$($env:COMPUTERNAME): creating gmsa" Add-KdsRootKey -EffectiveTime (Get-Date).AddHours(-10) | Out-Null # T0 New-ADServiceAccount -Name "T0-SQLSvc" -DNSHostName "T0-SQLSvc.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T0-SQLServers" New-ADServiceAccount -Name "T0-SQLAgt" -DNSHostName "T0-SQLAgt.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T0-SQLServers" # T0\ADFS New-ADServiceAccount -Name "T0-ADFSSvc" -DNSHostName "T0-ADFSSvc.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T0-ADFSServers" New-ADServiceAccount -Name "T0-ADFSSQLSvc" -DNSHostName "T0-ADFSSQLSvc.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T0-ADFSSQLServers" New-ADServiceAccount -Name "T0-ADFSSQLAgt" -DNSHostName "T0-ADFSSQLAgt.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T0-ADFSSQLServers" # T0\ADSync New-ADServiceAccount -Name "T0-ADSyncSvc" -DNSHostName "T0-ADSyncSvc.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T0-ADSyncServers" New-ADServiceAccount -Name "T0-ADSyncSQLSvc" -DNSHostName "T0-ADSyncSQLSvc.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T0-ADSyncSQLServers" New-ADServiceAccount -Name "T0-ADSyncSQLAgt" -DNSHostName "T0-ADSyncSQLAgt.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T0-ADSyncSQLServers" # T1 New-ADServiceAccount -Name "T1-SQLSvc" -DNSHostName "T1-SQLSvc.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T1-SQLServers" New-ADServiceAccount -Name "T1-SQLAgt" -DNSHostName "T1-SQLAgt.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T1-SQLServers" # T1\ConfigMgr New-ADServiceAccount -Name "T1-CMSQLSvc" -DNSHostName "T1-CMSQLSvc.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T1-CMSQLServers" New-ADServiceAccount -Name "T1-CMSQLAgt" -DNSHostName "T1-CMSQLAgt.$($Domain.Forest)" -PrincipalsAllowedToRetrieveManagedPassword "T1-CMSQLServers" } catch { throw "error creating gmsa: $($PSItem.Exception.Message)" } #endregion #region group membership try { Write-Output "$($env:COMPUTERNAME): creating group membership" # Tier Groups $Tier0AllowedGroups = 'T0-CloudAdmins', 'T0-DomAdmins', 'T0-EntAdmins', 'T0-RODCAdmins', 'T0-SchAdmins', 'T0-PKIAdmins', 'T0-PKISCAgents', 'T0-SQLAdmins', 'T0-ADFSAdmins', 'T0-ADSyncAdmins' $Tier1AllowedGroups = 'T1-CMAdmins', 'T1-ExAdmins', 'T1-ExAllowed', 'T1-ExMaintenance', 'T1-ExMBXAdmins', 'T1-ExOrgAdmins', 'T1-SQLAdmins' $Tier2AllowedGroups = 'T2-HelpDesk' Add-ADGroupMember -Identity 'T0-Allowed' -Members $Tier0AllowedGroups Add-ADGroupMember -Identity 'T0-Denied' -Members ($Tier1AllowedGroups + $Tier2AllowedGroups) Add-ADGroupMember -Identity 'T1-Allowed' -Members $Tier1AllowedGroups Add-ADGroupMember -Identity 'T1-Denied' -Members ($Tier0AllowedGroups + $Tier2AllowedGroups) Add-ADGroupMember -Identity 'T2-Allowed' -Members $Tier2AllowedGroups Add-ADGroupMember -Identity 'T2-Denied' -Members ($Tier0AllowedGroups + $Tier1AllowedGroups) # PAW Add-ADGroupMember -Identity 'PAW-Maintenance' -Members 'PAWMan1' # T0 Add-ADGroupMember -Identity 'Domain Admins' -Members 'T0-DomAdmin1' Add-ADGroupMember -Identity 'Enterprise Admins' -Members 'T0-EntAdmin1' Add-ADGroupMember -Identity 'T0-PKIAdmins' -Members 'T0-PKIAdmin1' Add-ADGroupMember -Identity 'T0-PKISCAgents' -Members 'T0-PKISCA1' Add-ADGroupMember -Identity 'T0-SQLAdmins' -Members 'T0-SQLAdmin1', 'T0-SQLAdmin2' # T1 Add-ADGroupMember -Identity 'T1-ExAdmins' -Members 'T1-ExAdmin1', 'T1-ExAdmin2' Add-ADGroupMember -Identity 'T1-CMAdmins' -Members 'T1-CMAdmin1', 'T1-CMAdmin2' Add-ADGroupMember -Identity 'T1-SQLAdmins' -Members 'T1-SQLAdmin1', 'T1-SQLAdmin2' # T2 Add-ADGroupMember -Identity 'T2-HelpDesk' -Members 'T2-HelpDesk1', 'T2-HelpDesk2' } catch { throw "error creating group memberships: $($PSItem.Exception.Message)" } #endregion } function Repair-DFSRReplication { <# .Description this will forcefully repair dfsr replication on the domain, use with care .Example # this will forcefully repair dfsr replication on the domain Repair-DFSRReplication .NOTES must be executed on a domain controller #> $Domain = (Get-ADDomain -Current LoggedOnUser).DistinguishedName $PDC = (Get-ADDomain).PDCEmulator $DCs = Get-ADObject -Filter { (objectclass -eq "computer") } -SearchBase "OU=Domain Controllers,$Domain" -Properties Name | Sort-Object Name $DCs | ForEach-Object { Invoke-Command -ComputerName $PSItem.Name -ScriptBlock { $Domain = (Get-ADDomain -Current LoggedOnUser).DistinguishedName $PDC = (Get-ADDomain).PDCEmulator $sysvolADObject = Get-ADObject -Filter { (objectclass -eq "msDFSR-Subscription") } ` -SearchBase "CN=SYSVOL Subscription,CN=Domain System Volume,CN=DFSR-LocalSettings,CN=$($env:COMPUTERNAME),OU=Domain Controllers,$Domain"` -Properties "msDFSR-Options", "msDFSR-Enabled" Write-Output "$($env:COMPUTERNAME): Restarting NIC to correct Network Profile" $NetAdapter = Get-NetAdapter $NetAdapter[0] | Restart-NetAdapter -Confirm:$false Write-Output "$($env:COMPUTERNAME): Installing DFSR Management Tools" Install-WindowsFeature -Name RSAT-DFS-Mgmt-Con | Out-Null if ($($PDC).Contains($env:COMPUTERNAME)) { Write-Output "$($env:COMPUTERNAME): Setting msDFSR-Options to 1" Set-ADObject -Identity $sysvolADObject.DistinguishedName -Replace @{"msDFSR-Options" = 1 } } Write-Output "$($env:COMPUTERNAME): Setting msDFSR-Enabled to false" Set-ADObject -Identity $sysvolADObject.DistinguishedName -Replace @{"msDFSR-Enabled" = $false } } } Write-Output "$($env:COMPUTERNAME): Forcing AD Replication" repadmin /syncall /P /e Start-Sleep(10) Invoke-Command -ComputerName $PDC -ScriptBlock { $Domain = (Get-ADDomain -Current LoggedOnUser).DistinguishedName $sysvolADObject = Get-ADObject -Filter { (objectclass -eq "msDFSR-Subscription") } ` -SearchBase "CN=SYSVOL Subscription,CN=Domain System Volume,CN=DFSR-LocalSettings,CN=$($env:COMPUTERNAME),OU=Domain Controllers,$Domain"` -Properties "msDFSR-Options", "msDFSR-Enabled" Write-Output "$($env:COMPUTERNAME): Restarting DFSR Service" Restart-Service DFSR Start-Sleep(20) Write-Output "$($env:COMPUTERNAME): Setting msDFSR-Enabled to true" Set-ADObject -Identity $sysvolADObject.DistinguishedName -Replace @{"msDFSR-Enabled" = $true } Write-Output "$($env:COMPUTERNAME): Forcing AD Replication" repadmin /syncall /P /e Start-Sleep(10) Write-Output "$($env:COMPUTERNAME): Executing DFSRDIAG" dfsrdiag pollad } $DCs | ForEach-Object { Invoke-Command -ComputerName $PSItem.Name -ScriptBlock { $Domain = (Get-ADDomain -Current LoggedOnUser).DistinguishedName $PDC = (Get-ADDomain).PDCEmulator $sysvolADObject = Get-ADObject -Filter { (objectclass -eq "msDFSR-Subscription") } ` -SearchBase "CN=SYSVOL Subscription,CN=Domain System Volume,CN=DFSR-LocalSettings,CN=$($env:COMPUTERNAME),OU=Domain Controllers,$Domain"` -Properties "msDFSR-Options", "msDFSR-Enabled" if (!$($PDC).Contains($($env:COMPUTERNAME))) { Write-Output "$($env:COMPUTERNAME): Restarting DFSR Service" Restart-Service DFSR Start-Sleep(20) Write-Output "$($env:COMPUTERNAME): Setting msDFSR-Enabled to true" Set-ADObject -Identity $sysvolADObject.DistinguishedName -Replace @{"msDFSR-Enabled" = $true } Write-Output "$($env:COMPUTERNAME): Executing DFSRDIAG" dfsrdiag pollad } } } } function Set-Interface { <# .Description configures the network interface, ip, dns, gateway .Parameter InterfaceObject nic objects .Parameter IPAddress ipaddress .Parameter NetPrefix net prefix, e.g. 24 .Parameter DefaultGateway default gateway in the subnet .Parameter DNSAddresses dns server addresses .Parameter NewName new name of the network adapter .Example # configures the specified network card Set-Interface -InterfaceObject $SFP10G_NICs[0] -IPAddress $CLU1_IPAddress -NetPrefix $NetPrefix -DefaultGateway $CLU_DefaultGateway -DNSAddresses $CLU_DNSAddresses -NewName "Datacenter-1" .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $InterfaceObject, [Parameter(Mandatory = $true)] $IPAddress, [Parameter(Mandatory = $true)] $NetPrefix, [Parameter(Mandatory = $false)] $DefaultGateway, [Parameter(Mandatory = $false)] $DNSAddresses, [Parameter(Mandatory = $false)] $NewName ) try { Write-Host "$($env:COMPUTERNAME): configuring nic with macaddress '$($InterfaceObject.MacAddress)'" If (($InterfaceObject | Get-NetIPConfiguration).IPv4Address.IPAddress) { $InterfaceObject | Remove-NetIPAddress -AddressFamily "IPv4" -Confirm:$false } If (($InterfaceObject | Get-NetIPConfiguration).Ipv4DefaultGateway) { $InterfaceObject | Remove-NetRoute -AddressFamily "IPv4" -Confirm:$false } Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\$($InterfaceObject.InterfaceGuid)" -Name EnableDHCP -Value 0 Start-Sleep -Seconds 2 if ($null -ne $DefaultGateway) { $InterfaceObject | New-NetIPAddress -IPAddress $IPAddress -AddressFamily "IPv4" -PrefixLength $NetPrefix -DefaultGateway $DefaultGateway | Out-Null Write-Host "$($env:COMPUTERNAME): nic with mac '$($InterfaceObject.MacAddress)' has static ip '$($IPAddress)'" } else { $InterfaceObject | New-NetIPAddress -IPAddress $IPAddress -AddressFamily "IPv4" -PrefixLength $NetPrefix | Out-Null Write-Host "$($env:COMPUTERNAME): nic with mac '$($InterfaceObject.MacAddress)' has static ip '$($IPAddress)'" } if ($null -ne $DNSAddresses) { $InterfaceObject | Set-DnsClientServerAddress -ServerAddresses $DNSAddresses } if ($null -ne $NewName) { $InterfaceObject | Rename-NetAdapter -NewName $NewName Write-Host "$($env:COMPUTERNAME): nic with mac '$($InterfaceObject.MacAddress)' renamed to '$($NewName)'" } } catch { throw "error setting '$($InterfaceObject.Name): $($PSItem.Exception.Message)" } } function Install-WADK { <# .Description this function can be used to install Windows ADK and Windows ADK PE .Parameter Latest use if you want the latest version .Parameter Features a list of Windows ADK to be installed .Parameter IncludeWinPE use if you want windows adk pe installed .Parameter Outpath path where the install and log files are saved .Example # installs windows adk and windows adk pe for configmgr site server Install-WADK -Latest -IncludeWinPE -Features OptionId.DeploymentTools, OptionId.UserStateMigrationTool .NOTES requires internet connection #> [CmdletBinding()] param ( [Parameter(ParameterSetName = 'Latest')] [switch] $Latest, [Parameter(ParameterSetName = 'Latest', Mandatory = $true)] [ValidateSet( "OptionId.ApplicationCompatibilityToolkit", "OptionId.DeploymentTools", "OptionId.ImagingAndConfigurationDesigner", "OptionId.ICDConfigurationDesigner", "OptionId.UserStateMigrationTool", "OptionId.VolumeActivationManagementTool", "OptionId.WindowsPerformanceToolkit", "OptionId.UEVTools", "OptionId.AppmanSequencer", "OptionId.AppmanAutoSequencer", "OptionId.MediaeXperienceAnalyzer", "OptionId.MediaeXperienceAnalyzer", "OptionId.WindowsAssessmentToolkit" )] [string[]] $Features, [Parameter(ParameterSetName = 'Latest')] [switch] $IncludeWinPE, [Parameter(ParameterSetName = 'Latest')] [string] $Outpath = "C:\Programdata\NTS\Windows_ADK" ) if ($Latest -eq $true) { $WADK_Download_URL_Latest = "https://go.microsoft.com/fwlink/?linkid=2196127" $WADK_PE_Download_URL_Latest = "https://go.microsoft.com/fwlink/?linkid=2196224" } else { throw "other version than latest are currently not supported" } try { if ((Test-Path -Path $Outpath) -eq $false) { New-Item -Path $Outpath -ItemType Directory -Force | Out-Null } $Outpath = (Get-Item -Path $Outpath).FullName if ($Features.count -gt 1) { $Features | ForEach-Object { [string]$Features_Selected = $Features_Selected + " " + $PSItem } } $WADK_LatestPath = "$($Outpath)\adksetup-latest.exe" $WADK_PE_LatestPath = "$($Outpath)\adkwinpesetup-latest.exe" Write-Output "$($env:COMPUTERNAME): downloading adk setup files" Invoke-WebRequest -UseBasicParsing -Uri $WADK_Download_URL_Latest -OutFile $WADK_LatestPath Write-Output "$($env:COMPUTERNAME): installing adk with the features $($Features_Selected)" $Process = Start-Process -FilePath $WADK_LatestPath -ArgumentList "/quiet /norestart /features $($Features_Selected) /l $($Outpath)\install-adksetup-latest.log" -NoNewWindow -Wait if ($Process.ExitCode -ne 0) { throw "check log at $($Outpath)\install-adksetup-latest.log" } if ($IncludeWinPE -eq $true) { Write-Output "$($env:COMPUTERNAME): downloading adk pe setup files" Invoke-WebRequest -UseBasicParsing -Uri $WADK_PE_Download_URL_Latest -OutFile $WADK_PE_LatestPath Write-Output "$($env:COMPUTERNAME): installing adk pe" $Process = Start-Process -FilePath $WADK_PE_LatestPath -ArgumentList "/quiet /norestart /features OptionId.WindowsPreinstallationEnvironment /l $($Outpath)\install-adkwinpesetup-latest.log" -NoNewWindow -Wait if ($Process.ExitCode -ne 0) { throw "check log at $($Outpath)\install-adkwinpesetup-latest.log" } } } catch { throw $PSItem.Exception.Message } } function Initialize-CM_MP_Prereq { <# .Description use this function to install configmgr management point prerequesits .Example # install configmgr management point prerequesits Initialize-CM_MP_Prereq .NOTES #> $Features = @( "FileAndStorage-Services" "Storage-Services" "Web-Server" "Web-WebServer" "Web-Common-Http" "Web-Default-Doc" "Web-Dir-Browsing" "Web-Http-Errors" "Web-Static-Content" "Web-Http-Redirect" "Web-DAV-Publishing" "Web-Health" "Web-Http-Logging" "Web-Custom-Logging" "Web-Log-Libraries" "Web-ODBC-Logging" "Web-Request-Monitor" "Web-Http-Tracing" "Web-Performance" "Web-Stat-Compression" "Web-Dyn-Compression" "Web-Security" "Web-Filtering" "Web-Basic-Auth" "Web-CertProvider" "Web-Client-Auth" "Web-Digest-Auth" "Web-Cert-Auth" "Web-IP-Security" "Web-Url-Auth" "Web-Windows-Auth" "Web-App-Dev" "Web-Net-Ext" "Web-Net-Ext45" "Web-AppInit" "Web-ASP" "Web-Asp-Net" "Web-Asp-Net45" "Web-CGI" "Web-ISAPI-Ext" "Web-ISAPI-Filter" "Web-Includes" "Web-WebSockets" "Web-Ftp-Server" "Web-Ftp-Service" "Web-Ftp-Ext" "Web-Mgmt-Tools" "Web-Mgmt-Console" "Web-Mgmt-Compat" "Web-Metabase" "Web-Lgcy-Mgmt-Console" "Web-Lgcy-Scripting" "Web-WMI" "Web-Scripting-Tools" "Web-Mgmt-Service" "NET-Framework-Features" "NET-Framework-Core" "NET-Framework-45-Features" "NET-Framework-45-Core" "NET-Framework-45-ASPNET" "NET-WCF-Services45" "NET-WCF-HTTP-Activation45" "NET-WCF-MSMQ-Activation45" "NET-WCF-Pipe-Activation45" "NET-WCF-TCP-Activation45" "NET-WCF-TCP-PortSharing45" "BITS" "BITS-IIS-Ext" "BITS-Compact-Server" "MSMQ" "MSMQ-Services" "MSMQ-Server" "Windows-Defender" "RDC" "RSAT" "RSAT-Feature-Tools" "RSAT-Bits-Server" "System-DataArchiver" "PowerShellRoot" "PowerShell" "PowerShell-V2" "WAS" "WAS-Process-Model" "WAS-Config-APIs" "WoW64-Support" "XPS-Viewer" ) try { Write-Output "$($env:COMPUTERNAME): installing required features for configmgr mp" foreach ($Feature in $Features) { Install-WindowsFeature -Name $Feature | Out-Null } } catch { throw $PSItem.Exception.Message } } function Initialize-CM_DP_Prereq { <# .Description use this function to install configmgr distribution point prerequesits .Example # install configmgr distribution point prerequesits Initialize-CM_DP_Prereq .NOTES #> try { Write-Output "$($env:COMPUTERNAME): creating NO_SMS_ON_DRIVE.SMS on boot volume" New-Item -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS" -ItemType File | Out-Null $Features = @( "FileAndStorage-Services" "File-Services" "FS-FileServer" "Storage-Services" "Web-Server" "Web-WebServer" "Web-Common-Http" "Web-Default-Doc" "Web-Dir-Browsing" "Web-Http-Errors" "Web-Static-Content" "Web-Http-Redirect" "Web-Health" "Web-Http-Logging" "Web-Performance" "Web-Stat-Compression" "Web-Security" "Web-Filtering" "Web-Windows-Auth" "Web-App-Dev" "Web-ISAPI-Ext" "Web-Mgmt-Tools" "Web-Mgmt-Console" "Web-Mgmt-Compat" "Web-Metabase" "Web-WMI" "Web-Scripting-Tools" "NET-Framework-45-Features" "NET-Framework-45-Core" "NET-WCF-Services45" "NET-WCF-TCP-PortSharing45" "Windows-Defender" "RDC" "System-DataArchiver" "PowerShellRoot" "PowerShell" "WoW64-Support" "XPS-Viewer" ) Write-Output "$($env:COMPUTERNAME): installing required features for configmgr dp" foreach ($Feature in $Features) { Install-WindowsFeature -Name $Feature | Out-Null } } catch { throw $PSItem.Exception.Message } } function Initialize-CM_SiteServer_Prereq { <# .Description use this function to install configmgr site server prerequesits .Example # install configmgr site server prerequesits Initialize-CM_SiteServer_Prereq .NOTES #> try { Write-Output "$($env:COMPUTERNAME): creating NO_SMS_ON_DRIVE.SMS on boot volume" New-Item -Path "$($env:SystemDrive)\NO_SMS_ON_DRIVE.SMS" -ItemType File | Out-Null Write-Output "$($env:COMPUTERNAME): installing required featuresfor configmgr site server" Install-WindowsFeature -Name RDC,UpdateServices-RSAT -IncludeAllSubFeature | Out-Null } catch { throw $PSItem.Exception.Message } } function Initialize-CM_SUP_Prereq { <# .Description use this function to install configmgr software update point prerequesits .Example # install configmgr software update point prerequesits Initialize-CM_SUP_Prereq .NOTES #> try { Write-Output "$($env:COMPUTERNAME): installing required features for configmgr sup" Install-WindowsFeature -Name RDC,UpdateServices-RSAT -IncludeAllSubFeature | Out-Null } catch { throw $PSItem.Exception.Message } } function Install-WSUS { <# .Description this will install the required functions for wsus and do the post install tasks .Parameter UseWID wsus with windows internal database .Parameter UseSQL wsus with mssql database .Parameter WSUSFilePath where should the file be stored .Parameter SQLInstance sql instance for wsus .Example # configures the specified network card Set-Interface -InterfaceObject $SFP10G_NICs[0] -IPAddress $CLU1_IPAddress -NetPrefix $NetPrefix -DefaultGateway $CLU_DefaultGateway -DNSAddresses $CLU_DNSAddresses -NewName "Datacenter-1" .NOTES https://smsagent.blog/2014/02/07/installing-and-configuring-wsus-with-powershell/ #> [CmdletBinding()] param ( [Parameter(ParameterSetName = 'WID')] [switch] $UseWID, [Parameter(ParameterSetName = 'SQL')] [switch] $UseSQL, [Parameter(ParameterSetName = 'WID', Mandatory = $true)] [Parameter(ParameterSetName = 'SQL', Mandatory = $true)] [string] $WSUSFilePath, [Parameter(ParameterSetName = 'SQL', Mandatory = $true)] [string] $SQLInstance # "MyServer\MyInstance" ) if ((Test-Path -Path $WSUSFilePath) -eq $false) { New-Item -Path $WSUSFilePath -ItemType Directory -Force | Out-Null } try { if ($UseWID -eq $true) { Write-Output "$($env:COMPUTERNAME): installing required features for wsus" Install-WindowsFeature UpdateServices -IncludeManagementTools -WarningVariable SilentlyContinue | Out-Null Write-Output "$($env:COMPUTERNAME): doing postinstall with wid" Start-Process -FilePath "$($env:ProgramFiles)\Update Services\Tools\wsusutil.exe" -ArgumentList "postinstall CONTENT_DIR=$($WSUSFilePath)" -NoNewWindow -Wait } elseif ($UseSQL -eq $true) { Write-Output "$($env:COMPUTERNAME): installing required features for wsus" Install-WindowsFeature -Name UpdateServices-Services,UpdateServices-DB -IncludeManagementTools -WarningVariable SilentlyContinue | Out-Null Write-Output "$($env:COMPUTERNAME): doing postinstall with sql instance $($SQLInstance)" Start-Process -FilePath "$($env:ProgramFiles)\Update Services\Tools\wsusutil.exe" -ArgumentList "postinstall SQL_INSTANCE_NAME=$($SQLInstance) CONTENT_DIR=$($WSUSFilePath)" -NoNewWindow -Wait } } catch { throw $PSItem.Exception.Message } } |