From ced0b2f10458f004bc8d4c4ba43a80929db38e03 Mon Sep 17 00:00:00 2001 From: Jaromirm Date: Thu, 22 Jun 2023 13:42:43 +0200 Subject: [PATCH 1/5] storage maintenance mode removed --- .../Scenario.ps1 | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/Scenarios/AzSHCI and Dell Servers Update/Scenario.ps1 b/Scenarios/AzSHCI and Dell Servers Update/Scenario.ps1 index cc2a6fd4..5ea9e873 100644 --- a/Scenarios/AzSHCI and Dell Servers Update/Scenario.ps1 +++ b/Scenarios/AzSHCI and Dell Servers Update/Scenario.ps1 @@ -279,9 +279,17 @@ if ($offline){ #region apply updates on nodes foreach ($Node in $Nodes){ - #make sure there is no maintenance mode - Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Disabling maintenance mode - if there is any" - Get-StorageFaultDomain -Type StorageScaleUnit -CimSession $ClusterName | Disable-StorageMaintenanceMode -CimSession $ClusterName + #make sure all nodes are up + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') Make sure all nodes are up" + $NodesNotUp=Get-ClusterNode -Cluster $ClusterName | Where-Object state -ne up + if ($NodesNotUp){ + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') Some nodes were not up, resuming : $($NodesNotUp.Name)" + $NodesNotUp | Resume-ClusterNode -Failback Immediate | Out-Null + if (Get-ClusterNode -Cluster $ClusterName | Where-Object state -ne up){ + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') Resuming nodes failed. Interrupting" + break + } + } #check for repair jobs, if found, wait until finished Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Waiting for Storage jobs to finish" @@ -368,11 +376,13 @@ if ($offline){ $MSUpdateInstallResult=$Null } } + }else{ + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Skipping Microsoft Updates as requested" } #Suspend node Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Suspending Cluster Node" - Suspend-ClusterNode -Name "$Node" -Cluster $ClusterName -Drain -Wait | Out-Null + Suspend-ClusterNode -Name "$Node" -Cluster $ClusterName -Drain -Wait -ErrorAction Ignore | Out-Null if (Get-ClusterResource -Cluster $ClusterName | Where-Object OwnerNode -eq $Node | Where-Object State -eq "Online"){ Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Suspending Cluster Node Failed. Resuming and terminating patch run" @@ -380,9 +390,9 @@ if ($offline){ break } - #enable storage maintenance mode - Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Enabling Storage Maintenance mode" - Get-StorageFaultDomain -CimSession $ClusterName -FriendlyName $Node | Enable-StorageMaintenanceMode -CimSession $ClusterName + #enable storage maintenance mode (not needed as node goes to maintenance mode when suspended) + #Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Enabling Storage Maintenance mode" + #Get-StorageFaultDomain -CimSession $ClusterName -FriendlyName $Node | Enable-StorageMaintenanceMode -CimSession $ClusterName #Install Dell updates https://dl.dell.com/content/manual36290092-dell-emc-system-update-version-1-9-3-0-user-s-guide.pdf?language=en-us&ps=true #assuming dell updates might interrupt server, therefore it's done during maintenance mode @@ -401,11 +411,13 @@ if ($offline){ } } } + }else{ + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Skipping Dell Updates as requested" } #Check if reboot is required and reboot if (($Compliance | Where-Object {$_.NodeName -eq $Node -and $_.rebootrequired -eq $True}) -or ($Scanresult | Where-Object ($_.ComputerName -eq $node -and $_.MicrosoftUpdateRequired -eq $True)) -or ($ForceReboot -eq $True)){ - Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Reboot is required" + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Reboot is requested" #restart node and wait for PowerShell to come up (with powershell 7 you need to wait for WINRM :) Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Restarting Cluster Node" Restart-Computer -ComputerName $Node -Protocol WSMan -Wait -For PowerShell -Force | Out-Null @@ -417,9 +429,9 @@ if ($offline){ Start-Sleep 5 }while($state -ne "Paused") - #disable storage maintenance mode - Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Disabling Storage Maintenance mode" - Get-StorageFaultDomain -Type StorageScaleUnit -CimSession $Node | Where-Object FriendlyName -eq $Node | Disable-StorageMaintenanceMode -CimSession $Node + #disable storage maintenance mode (not needed as node resumes from maintenance mode when resumed) + #Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Disabling Storage Maintenance mode" + #Get-StorageFaultDomain -Type StorageScaleUnit -CimSession $Node | Where-Object FriendlyName -eq $Node | Disable-StorageMaintenanceMode -CimSession $Node #resume cluster node Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Resuming Cluster Node" @@ -430,6 +442,12 @@ if ($offline){ do {Start-Sleep 5}while( Get-CimInstance -CimSession $Nodes -Namespace root\virtualization\v2 -ClassName Msvm_MigrationJob | Where-Object StatusDescriptions -eq "Job is running" ) + + #wait for node to resume from maintenance mode + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') $($Node): Waiting for node to resume from Maintenance mode" + do {Start-Sleep 5}while( + Get-StorageFaultDomain -CimSession $Node | Where-Object OperationalStatus -eq "In Maintenance Mode" + ) } #cleanup updates folder on nodes From 22101e48432946b9dc0333b6dbd52cbeb2ebdc58 Mon Sep 17 00:00:00 2001 From: Jaromirm Date: Wed, 5 Jul 2023 01:40:53 +0200 Subject: [PATCH 2/5] new scenario --- .../AzSHCI and Arc-enabled VMs/LabConfig.ps1 | 14 - .../AzSHCI and Arc-enabled VMs/Scenario.ps1 | 592 ------------------ .../AzSHCI and Resource Bridge/LabConfig.ps1 | 14 + .../AzSHCI and Resource Bridge/Scenario.ps1 | 487 ++++++++++++++ 4 files changed, 501 insertions(+), 606 deletions(-) delete mode 100644 Scenarios/AzSHCI and Arc-enabled VMs/LabConfig.ps1 delete mode 100644 Scenarios/AzSHCI and Arc-enabled VMs/Scenario.ps1 create mode 100644 Scenarios/AzSHCI and Resource Bridge/LabConfig.ps1 create mode 100644 Scenarios/AzSHCI and Resource Bridge/Scenario.ps1 diff --git a/Scenarios/AzSHCI and Arc-enabled VMs/LabConfig.ps1 b/Scenarios/AzSHCI and Arc-enabled VMs/LabConfig.ps1 deleted file mode 100644 index b4011ac1..00000000 --- a/Scenarios/AzSHCI and Arc-enabled VMs/LabConfig.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -$LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!' ; Prefix = 'MSLab-' ; DCEdition='4'; Internet=$true ; TelemetryLevel='Full' ; TelemetryNickname='' ; AdditionalNetworksConfig=@(); VMs=@()} - -#2 nodes for AzSHCI Cluster -1..2 | ForEach-Object {$VMNames="ArcVMs" ; $LABConfig.VMs += @{ VMName = "$VMNames$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI21H2_G2.vhdx' ; HDDNumber = 4 ; HDDSize= 4TB ; MemoryStartupBytes= 14GB; VMProcessorCount="Max" ; NestedVirt=$true ; VirtualTPM=$true}} - -#or 2 nodes for Windows Server 2022 -#1..2 | ForEach-Object {$VMNames="ArcVMs" ; $LABConfig.VMs += @{ VMName = "$VMNames$_" ; Configuration = 'S2D' ; ParentVHD = 'Win2022Core_G2.vhdx' ; HDDNumber = 4 ; HDDSize= 4TB ; MemoryStartupBytes= 14GB; VMProcessorCount="Max" ; NestedVirt=$true ; VirtualTPM=$true}} - -#optional Windows Admin Center gateway -#$LabConfig.VMs += @{ VMName = 'WACGW' ; ParentVHD = 'Win2022Core_G2.vhdx' ; MGMTNICs=1 } - -#optional Windows Management machine -#$LabConfig.VMs += @{ VMName = 'Management' ; ParentVHD = 'Win2022_G2.vhdx' ; MGMTNICs=1 } - diff --git a/Scenarios/AzSHCI and Arc-enabled VMs/Scenario.ps1 b/Scenarios/AzSHCI and Arc-enabled VMs/Scenario.ps1 deleted file mode 100644 index efc744ea..00000000 --- a/Scenarios/AzSHCI and Arc-enabled VMs/Scenario.ps1 +++ /dev/null @@ -1,592 +0,0 @@ -#https://aka.ms/ArcEnabledHCI -#https://aka.ms/AzureArcVM - -################### -### Run from DC ### -################### - -#region Variables - $ClusterNodeNames="ArcVMs1","ArcVMs2" - $ClusterName="ArcVMs-Cluster" - $vswitchName="vSwitch" - $controlPlaneIP="10.0.0.111" - $VolumeName="MOC" - $VolumePath="c:\ClusterStorage\$VolumeName" - $CredSSPUserName="CORP\LabAdmin" - $CredSSPPassword="LS1setup!" - $CustomLocationName="$ClusterName-cl" - $LibraryVolumeName="Library" #volume for Gallery images for VMs - $AzureImages=@() - $AzureImages+=@{PublisherName = "microsoftwindowsserver";Offer="windowsserver";SKU="2022-datacenter-azure-edition-smalldisk";OSType="Windows"} #OS TYpe can be "Windows" or "Linux" - first letter has to be capital! - $AzureImages+=@{PublisherName = "microsoftwindowsserver";Offer="windowsserver";SKU="2022-datacenter-azure-edition-core-smalldisk";OSType="Windows"} #OS TYpe can be "Windows" or "Linux" - first letter has to be capital! - - #Install or update Azure packages - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force - $ModuleNames="Az.Accounts","Az.Compute","Az.Resources","Az.StackHCI" - foreach ($ModuleName in $ModuleNames){ - $Module=Get-InstalledModule -Name $ModuleName -ErrorAction Ignore - if ($Module){$LatestVersion=(Find-Module -Name $ModuleName).Version} - if (-not($Module) -or ($Module.Version -lt $LatestVersion)){ - Install-Module -Name $ModuleName -Force - } - } - - #login to Azure - if (-not (Get-AzContext)){ - Login-AzAccount -UseDeviceAuthentication - } - - $ResourceGroupName="$ClusterName-rg" - $SubscriptionID=(Get-AzContext).Subscription.ID - - #select context - $context=Get-AzContext -ListAvailable - if (($context).count -gt 1){ - $context=$context | Out-GridView -OutputMode Single - $context | Set-AzContext - } - - #I did only test same for all (EastUS) - $VMImageLocation="eastus" - $ArcResourceBridgeLocation="eastus" - $AzureStackLocation="eastus" - - <#or populate by choosing your own - #grab region where to grab VMs from - $VMImageLocation = (Get-AzLocation | Where-Object Providers -Contains "Microsoft.Compute" | Out-GridView -OutputMode Single -Title "Choose location where to grab VMs from").Location - - #grab location for Arc Resource Bridge and Custom location - $ArcResourceBridgeLocation=(Get-AzLocation | Where-Object Providers -Contains "Microsoft.ResourceConnector" | Out-GridView -OutputMode Single -Title "Choose location for Arc Resource Bridge and Custom location").Location - - #grab location for Azure Stack - $AzureStackLocation=(Get-AzLocation | Where-Object Providers -Contains "Microsoft.AzureStackHCI" | Out-GridView -OutputMode Single -Title "Choose location for Azure Stack HCI - where it should be registered").Location - #> - - #virtual network name - $vnetName=$vswitchName - -#endregion - -#region Create 2 node cluster (just simple. Not for prod - follow hyperconverged scenario for real clusters https://github.com/microsoft/MSLab/tree/master/Scenarios/AzSHCI%20Deployment) - # Install features for management on server - Install-WindowsFeature -Name RSAT-Clustering,RSAT-Clustering-Mgmt,RSAT-Clustering-PowerShell,RSAT-Hyper-V-Tools - - # Update servers (optional) - <# - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { - New-PSSessionConfigurationFile -RunAsVirtualAccount -Path $env:TEMP\VirtualAccount.pssc - Register-PSSessionConfiguration -Name 'VirtualAccount' -Path $env:TEMP\VirtualAccount.pssc -Force - } -ErrorAction Ignore - # Run Windows Update via ComObject. - Invoke-Command -ComputerName $ClusterNodeNames -ConfigurationName 'VirtualAccount' { - $Searcher = New-Object -ComObject Microsoft.Update.Searcher - $SearchCriteriaAllUpdates = "IsInstalled=0 and DeploymentAction='Installation' or - IsPresent=1 and DeploymentAction='Uninstallation' or - IsInstalled=1 and DeploymentAction='Installation' and RebootRequired=1 or - IsInstalled=0 and DeploymentAction='Uninstallation' and RebootRequired=1" - $SearchResult = $Searcher.Search($SearchCriteriaAllUpdates).Updates - $Session = New-Object -ComObject Microsoft.Update.Session - $Downloader = $Session.CreateUpdateDownloader() - $Downloader.Updates = $SearchResult - $Downloader.Download() - $Installer = New-Object -ComObject Microsoft.Update.Installer - $Installer.Updates = $SearchResult - $Result = $Installer.Install() - $Result - } - #remove temporary PSsession config - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { - Unregister-PSSessionConfiguration -Name 'VirtualAccount' - Remove-Item -Path $env:TEMP\VirtualAccount.pssc - } - #> - - # Install features on servers - Invoke-Command -computername $ClusterNodeNames -ScriptBlock { - Enable-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V -Online -NoRestart - Install-WindowsFeature -Name "Failover-Clustering","RSAT-Clustering-Powershell","Hyper-V-PowerShell" - } - - # restart servers - Restart-Computer -ComputerName $ClusterNodeNames -Protocol WSMan -Wait -For PowerShell - #failsafe - sometimes it evaluates, that servers completed restart after first restart (hyper-v needs 2) - Start-sleep 20 - - # create vSwitch (sometimes happens, that I need to restart servers again and then it will create vSwitch...) - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock {New-VMSwitch -Name $using:vswitchName -EnableEmbeddedTeaming $TRUE -NetAdapterName (Get-NetIPAddress -IPAddress 10.* ).InterfaceAlias} - - #create cluster - New-Cluster -Name $ClusterName -Node $ClusterNodeNames - Start-Sleep 5 - Clear-DNSClientCache - - #add file share witness - #Create new directory - $WitnessName=$ClusterName+"Witness" - Invoke-Command -ComputerName DC -ScriptBlock {new-item -Path c:\Shares -Name $using:WitnessName -ItemType Directory} - $accounts=@() - $accounts+="corp\$($ClusterName)$" - $accounts+="corp\Domain Admins" - New-SmbShare -Name $WitnessName -Path "c:\Shares\$WitnessName" -FullAccess $accounts -CimSession DC - #Set NTFS permissions - Invoke-Command -ComputerName DC -ScriptBlock {(Get-SmbShare $using:WitnessName).PresetPathAcl | Set-Acl} - #Set Quorum - Set-ClusterQuorum -Cluster $ClusterName -FileShareWitness "\\DC\$WitnessName" - - #Enable S2D - Enable-ClusterS2D -CimSession $ClusterName -Verbose -Confirm:0 - - #configure thin volumes a default if available (because why not :) - $OSInfo=Invoke-Command -ComputerName $ClusterName -ScriptBlock { - Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\' - } - if ($OSInfo.productname -eq "Azure Stack HCI" -and $OSInfo.CurrentBuild -ge 20348){ - Get-StoragePool -CimSession $ClusterName -FriendlyName S2D* | Set-StoragePool -ProvisioningTypeDefault Thin - } - - -#endregion - -#region Register Azure Stack HCI to Azure - if not registered, VMs are not added as cluster resources = AKS script will fail - #download Azure module - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force - if (!(Get-InstalledModule -Name Az.StackHCI -ErrorAction Ignore)){ - Install-Module -Name Az.StackHCI -Force - } - - #login to azure - #download Azure module - if (!(Get-InstalledModule -Name az.accounts -ErrorAction Ignore)){ - Install-Module -Name Az.Accounts -Force - } - Connect-AzAccount -UseDeviceAuthentication - - #select subscription if more available - $subscription=Get-AzSubscription - if (($subscription).count -gt 1){ - $subscription | Out-GridView -OutputMode Single | Set-AzContext - } - - #grab subscription ID - $subscriptionID=(Get-AzContext).Subscription.id - - <# Register AZSHCi without prompting for creds, - Notes: As Dec. 2021, in Azure Stack HCI 21H2, if you Register-AzStackHCI the cluster multiple times in same ResourceGroup (e.g. default - resource group name is AzSHCI-Cluster-rg) without run UnRegister-AzStackHCI first, although you may succeed in cluster registration, but - sever node Arc integration will fail, even if you have deleted the ResourceGroup in Azure Portal before running Register-AzStackHCI #> - - $armTokenItemResource = "https://management.core.windows.net/" - $graphTokenItemResource = "https://graph.windows.net/" - $azContext = Get-AzContext - $authFactory = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory - $graphToken = $authFactory.Authenticate($azContext.Account, $azContext.Environment, $azContext.Tenant.Id, $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $graphTokenItemResource).AccessToken - $armToken = $authFactory.Authenticate($azContext.Account, $azContext.Environment, $azContext.Tenant.Id, $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $armTokenItemResource).AccessToken - $id = $azContext.Account.Id - - #grab location - if (!(Get-InstalledModule -Name Az.Resources -ErrorAction Ignore)){ - Install-Module -Name Az.Resources -Force - } - - #register - Register-AzStackHCI -SubscriptionID $subscriptionID -Region $AzureStackLocation -ComputerName $ClusterName -GraphAccessToken $graphToken -ArmAccessToken $armToken -AccountId $id - - #Install Azure Stack HCI RSAT Tools to all nodes - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { - Install-WindowsFeature -Name RSAT-Azure-Stack-HCI - } - #Validate registration (query on just one node is needed) - Invoke-Command -ComputerName $ClusterName -ScriptBlock { - Get-AzureStackHCI - } -#endregion - -#region Install modules and create MOC agent Service - #Install required modules - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force - Install-Module -Name PowershellGet -Force -Confirm:$false -SkipPublisherCheck - Update-Module -Name PowerShellGet - - #to be able to install ArcHci and MOC, powershellget 2.2.5 needs to be used - to this posh restart is needed - Start-Process -Wait -FilePath PowerShell -ArgumentList { - Install-Module -Name MOC -Repository PSGallery -Force -AcceptLicense - Install-Module -Name ArcHci -Force -Confirm:$false -SkipPublisherCheck -AcceptLicense - } - - #Increase MaxEvenlope and create session to copy files to - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock {Set-Item -Path WSMan:\localhost\MaxEnvelopeSizekb -Value 4096} - - #distribute modules to cluster nodes - $ModuleNames="ArcHci","Moc","DownloadSDK" - $PSSessions=New-PSSession -ComputerName $ClusterNodeNames - Foreach ($PSSession in $PSSessions){ - Foreach ($ModuleName in $ModuleNames){ - Copy-Item -Path $env:ProgramFiles\windowspowershell\modules\$ModuleName -Destination $env:ProgramFiles\windowspowershell\modules -ToSession $PSSession -Recurse -Force - } - Foreach ($ModuleName in $RequiredModules.ModuleName){ - Copy-Item -Path $env:ProgramFiles\windowspowershell\modules\$ModuleName -Destination $env:ProgramFiles\windowspowershell\modules -ToSession $PSSession -Recurse -Force - } - } - - #Enable CredSSP - # Temporarily enable CredSSP delegation to avoid double-hop issue - foreach ($ClusterNodeName in $ClusterNodeNames){ - Enable-WSManCredSSP -Role "Client" -DelegateComputer $ClusterNodeName -Force - } - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Enable-WSManCredSSP Server -Force } - - $SecureStringPassword = ConvertTo-SecureString $CredSSPPassword -AsPlainText -Force - $Credentials = New-Object System.Management.Automation.PSCredential ($CredSSPUserName, $SecureStringPassword) - - #initialize MOC - Invoke-Command -ComputerName $ClusterNodeNames -Credential $Credentials -Authentication Credssp -ScriptBlock { - Initialize-MocNode - } - - #Create volume for MOC if does not exist - if (-not (Get-Volume -FriendlyName $VolumeName -CimSession $ClusterName -ErrorAction SilentlyContinue)) { - New-Volume -FriendlyName $VolumeName -CimSession $ClusterName -Size 1TB -StoragePoolFriendlyName S2D* - } - - #prepare arc resource bridge - #Configure MOC - Invoke-Command -ComputerName $ClusterNodeNames[0] -Credential $Credentials -Authentication Credssp -ScriptBlock { - $Vnet=New-MocNetworkSetting -Name hcirb-vnet -vswitchName $using:vswitchName -vipPoolStart $using:controlPlaneIP -vipPoolEnd $using:controlPlaneIP - Set-MocConfig -workingDir "\\$using:ClusterName\ClusterStorage$\$using:VolumeName\workingDir" -vnet $vnet -imageDir $using:VolumePath\imageStore -skipHostLimitChecks -cloudConfigLocation $using:VolumePath\cloudStore -catalog aks-hci-stable-catalogs-ext -ring stable - } - - #Install MOC Cloud Agent Service - Invoke-Command -ComputerName $ClusterNodeNames[0] -Credential $Credentials -Authentication Credssp -ScriptBlock { - Install-moc - } - - # Disable CredSSP - Disable-WSManCredSSP -Role Client - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Disable-WSManCredSSP Server } - -#endregion - -#region Create custom location and install Arc Resource Bridge - #Login to azure - if (!(Get-AzContext)){ - Connect-AzAccount -UseDeviceAuthentication - } - - #generate variables - #Grab registration info - $RegistrationInfo=Invoke-Command -ComputerName $CLusterName -ScriptBlock {Get-AzureStackHCI} - $AzureResourceUri= $RegistrationInfo.AzureResourceUri - $HCIResourceGroupName=$AzureResourceUri.split("/")[4] - $HCISubscriptionID=$AzureResourceUri.split("/")[2] - #create bridge resource name - $BridgeResourceName=("$($RegistrationInfo.AzureResourceName)-arcbridge").ToLower() - - #install Az CLI - #download - Start-BitsTransfer -Source https://aka.ms/installazurecliwindows -Destination $env:userprofile\Downloads\AzureCLI.msi - #install - Start-Process msiexec.exe -Wait -ArgumentList "/I $env:userprofile\Downloads\AzureCLI.msi /quiet" - #add az to enviromental variables so no posh restart is needed - [System.Environment]::SetEnvironmentVariable('PATH',$Env:PATH+';C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin') - - #add Az extensions - az extension add --name customlocation - az extension add --name azurestackhci - az extension add --name arcappliance - az extension add --name k8s-extension - az extension add --name connectedk8s - - #register namespaces - #register - $Providers="Microsoft.ExtendedLocation","Microsoft.ResourceConnector" - foreach ($Provider in $Providers){ - Register-AzResourceProvider -ProviderNamespace $Provider - } - #wait until resource providers are registered - foreach ($Provider in $Providers){ - do { - $Status=Get-AzResourceProvider -ProviderNamespace $Provider - Write-Output "Registration Status - $Provider : $(($status.RegistrationState -match 'Registered').Count)/$($Status.Count)" - Start-Sleep 1 - } while (($status.RegistrationState -match "Registered").Count -ne ($Status.Count)) - } - - #login with device authentication - az login --use-device-code - $allSubscriptions = (az account list | ConvertFrom-Json).ForEach({$_ | Select-Object -Property Name, id, tenantId }) - if (($allSubscriptions).Count -gt 1){ - $subscription = ($allSubscriptions | Out-GridView -OutputMode Single) - az account set --subscription $subscription.id - } - #create arc appliance - #generate config files - #Enable CredSSP - # Temporarily enable CredSSP delegation to avoid double-hop issue - foreach ($ClusterNodeName in $ClusterNodeNames){ - Enable-WSManCredSSP -Role "Client" -DelegateComputer $ClusterNodeName -Force - } - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Enable-WSManCredSSP Server -Force } - - $SecureStringPassword = ConvertTo-SecureString $CredSSPPassword -AsPlainText -Force - $Credentials = New-Object System.Management.Automation.PSCredential ($CredSSPUserName, $SecureStringPassword) - - Invoke-Command -ComputerName $ClusterNodeNames[0] -Credential $Credentials -Authentication Credssp -ScriptBlock { - New-ArcHciConfigFiles -subscriptionID $using:HCISubscriptionID -location $using:ArcResourceBridgeLocation -resourceGroup $using:HCIResourceGroupName -resourceName $using:BridgeResourceName -workDirectory "\\$using:ClusterName\ClusterStorage$\$using:VolumeName\workingDir" - } - # Disable CredSSP - Disable-WSManCredSSP -Role Client - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Disable-WSManCredSSP Server } - - #prepare - az arcappliance prepare hci --config-file \\$ClusterName\ClusterStorage$\$VolumeName\workingDir\hci-appliance.yaml - - #Create folder for config - New-Item -Path $env:USERPROFILE\.kube -ItemType Directory -ErrorAction Ignore - - #deploy control plane and export kube config - az arcappliance deploy hci --config-file \\$ClusterName\ClusterStorage$\$VolumeName\workingDir\hci-appliance.yaml --outfile $env:USERPROFILE\.kube\config - #create connection to Azure (might throw error, dont worry! It's being deployed on background) - az arcappliance create hci --config-file \\$ClusterName\ClusterStorage$\$VolumeName\workingDir\hci-appliance.yaml --kubeconfig $env:USERPROFILE\.kube\config - - #wait until appliance is running - do { - $Status=az arcappliance show --only-show-errors --resource-group $HCIResourceGroupName --name $BridgeResourceName | ConvertFrom-Json - Write-Host -NoNewline -Object "." - Start-Sleep 2 - } until ($status.status -match "Running") - - #verify if appliance is running - az arcappliance show --resource-group $HCIResourceGroupName --name $BridgeResourceName | ConvertFrom-Json - - #Add K8s extension - #create - az k8s-extension create --cluster-type appliances --cluster-name $BridgeResourceName --resource-group $HCIResourceGroupName --name hci-vmoperator --extension-type Microsoft.AZStackHCI.Operator --scope cluster --release-namespace helm-operator2 --configuration-settings Microsoft.CustomLocation.ServiceAccount=hci-vmoperator --configuration-protected-settings-file \\$ClusterName\ClusterStorage$\$VolumeName\workingDir\hci-config.json --configuration-settings HCIClusterID=$AzureResourceUri --auto-upgrade true - #validate - az k8s-extension show --cluster-type appliances --cluster-name $BridgeResourceName --resource-group $HCIResourceGroupName --name hci-vmoperator - - #Create custom location (has to be created after arcappliance deployment) - az customlocation create --resource-group $HCIResourceGroupName --name $CustomLocationName --cluster-extension-ids "/subscriptions/$HCISubscriptionID/resourceGroups/$HCIResourceGroupName/providers/Microsoft.ResourceConnector/appliances/$BridgeResourceName/providers/Microsoft.KubernetesConfiguration/extensions/hci-vmoperator" --namespace hci-vmoperator --host-resource-id "/subscriptions/$HCISubscriptionID/resourceGroups/$HCIResourceGroupName/providers/Microsoft.ResourceConnector/appliances/$BridgeResourceName" --location $ArcResourceBridgeLocation - - <# Or with PowerShell - #install Az.CustomLocation module - if (!(Get-InstalledModule -Name az.CustomLocation -ErrorAction Ignore)){ - Install-Module -Name Az.CustomLocation -Force - } - New-AzCustomLocation -ResourceGroupName $ResourceGroupName -Name $CustomLocationName -ClusterExtensionID "/subscriptions/$SubscriptionID/resourceGroups/$ResourceGroupName/providers/Microsoft.ResourceConnector/appliances/$BridgeResourceName/providers/Microsoft.KubernetesConfiguration/extensions/hci-vmoperator" -NameSpace hci-vmoperator -HostResourceID "/subscriptions/$SubscriptionID/resourceGroups/$ResourceGroupName/providers/Microsoft.ResourceConnector/appliances/$BridgeResourceName" -Location $ArcResourceBridgeLocation - #> - -#endregion - -#region Copy kube config to nodes to have it available there -$Sessions=New-PSSession -ComputerName $ClusterNodeNames - -#copy kube to cluster nodes -Foreach ($Session in $Sessions){ - Copy-Item -Path "$env:userprofile\.kube" -Destination $env:userprofile -ToSession $Session -Recurse -Force -} - -$Sessions | Remove-PSSession -#endregion - -#region create virtual network - #Grab registration info - $RegistrationInfo=Invoke-Command -ComputerName $CLusterName -ScriptBlock {Get-AzureStackHCI} - $AzureResourceUri= $RegistrationInfo.AzureResourceUri - $HCIResourceGroupName=$AzureResourceUri.split("/")[4] - $HCISubscriptionID=$AzureResourceUri.split("/")[2] - #create network - az azurestackhci virtualnetwork create --subscription $HCISubscriptionID --resource-group $HCIResourceGroupName --extended-location name="/subscriptions/$HCISubscriptionID/resourceGroups/$HCIResourceGroupName/providers/Microsoft.ExtendedLocation/customLocations/$CustomLocationName" type="CustomLocation" --location $ArcResourceBridgeLocation --network-type "Transparent" --name $vnetName -#endregion - -#region create images - #Create library volume - if (-not (Get-VirtualDisk -CimSession $ClusterName -FriendlyName $LibraryVolumeName -ErrorAction Ignore)){ - New-Volume -StoragePoolFriendlyName "S2D*" -FriendlyName $LibraryVolumeName -FileSystem CSVFS_ReFS -Size 500GB -ResiliencySettingName Mirror -CimSession $ClusterName - } - - #region download Azure images to library - #list windows server Offers - Get-AzVMImageSku -Location $VMImageLocation -PublisherName "microsoftwindowsserver" -Offer "WindowsServer" - - #Create managed disks with azure images - foreach ($AzureImage in $AzureImages){ - $image=Get-AzVMImage -Location $VMImageLocation -PublisherName $AzureImage.PublisherName -Offer $AzureImage.Offer -SKU $AzureImage.SKU | Sort-Object Version -Descending |Select-Object -First 1 - $ImageVersionID = $image.id - # Export the OS disk - $imageOSDisk = @{Id = $ImageVersionID} - $OSDiskConfig = New-AzDiskConfig -Location $VMImageLocation -CreateOption "FromImage" -ImageReference $imageOSDisk - New-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $AzureImage.SKU -Disk $OSDiskConfig - } - - #Download AZCopy - # Download the package - Start-BitsTransfer -Source "https://aka.ms/downloadazcopy-v10-windows" -Destination "$env:UserProfile\Downloads\AzCopy.zip" - Expand-Archive -Path "$env:UserProfile\Downloads\AzCopy.zip" -DestinationPath "$env:UserProfile\Downloads\AZCopy" -Force - $item=Get-ChildItem -Name azcopy.exe -Recurse -Path "$env:UserProfile\Downloads\AZCopy" - Move-Item -Path "$env:UserProfile\Downloads\AZCopy\$item" -Destination "$env:UserProfile\Downloads\" -Force - Remove-Item -Path "$env:UserProfile\Downloads\AZCopy\" -Recurse - Remove-Item -Path "$env:UserProfile\Downloads\AzCopy.zip" - - #Download Images - foreach ($AzureImage in $AzureImages){ - #Grant Access https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview - $output=Grant-AzDiskAccess -ResourceGroupName $ResourceGroupName -DiskName $AzureImage.SKU -Access 'Read' -DurationInSecond 3600 - #Grab shared access signature - $SAS=$output.accesssas - #Download - & $env:UserProfile\Downloads\azcopy.exe copy $sas "\\$ClusterName\ClusterStorage$\$LibraryVolumeName\$($AzureImage.SKU).vhd" --check-md5 NoCheck --cap-mbps 500 - #once disk is downloaded, disk access can be revoked - Revoke-AzDiskAccess -ResourceGroupName $ResourceGroupName -Name $AzureImage.SKU - #and disk itself can be removed - Remove-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $AzureImage.SKU -Force - #and disk itself can be converted to VHDx and compacted - Invoke-Command -ComputerName $ClusterName -ScriptBlock { - Convert-VHD -Path "c:\clusterstorage\$using:LibraryVolumeName\$($using:AzureImage.sku).vhd" -DestinationPath "c:\clusterstorage\$using:LibraryVolumeName\$($using:AzureImage.sku).vhdx" -VHDType Dynamic -DeleteSource - Optimize-VHD -Path "c:\clusterstorage\$using:LibraryVolumeName\$($using:AzureImage.sku).vhdx" -Mode Full - } - if ($AzureImage.SKU -like "*-smalldisk*"){ - #and it can be also expanded from default 32GB (since image is small to safe some space) - Invoke-Command -ComputerName $ClusterName -ScriptBlock { - Resize-VHD -Path "c:\clusterstorage\$using:LibraryVolumeName\$($using:AzureImage.sku).vhdx" -SizeBytes 127GB - #mount VHD - $VHDMount=Mount-VHD "c:\clusterstorage\$using:LibraryVolumeName\$($using:AzureImage.sku).vhdx" -Passthru - $partition = $vhdmount | Get-Disk | Get-Partition | Where-Object PartitionNumber -Eq 4 - $partition | Resize-Partition -Size ($Partition | Get-PartitionSupportedSize).SizeMax - $VHDMount| Dismount-VHD - } - #and since it's no longer smalldisk, it can be renamed - Invoke-Command -ComputerName $ClusterName -ScriptBlock { - $NewName=($using:AzureImage.SKU).replace("-smalldisk","") - Rename-Item -Path "c:\clusterstorage\$using:LibraryVolumeName\$($using:AzureImage.sku).vhdx" -NewName "$NewName.vhdx" - } - } - } - #endregion - - #region create gallery image offer - $RegistrationInfo=Invoke-Command -ComputerName $CLusterName -ScriptBlock {Get-AzureStackHCI} - $AzureResourceUri= $RegistrationInfo.AzureResourceUri - $HCIResourceGroupName=$AzureResourceUri.split("/")[4] - $HCISubscriptionID=$AzureResourceUri.split("/")[2] - foreach ($AzureImage in $AzureImages){ - #Since disk was expandend, -smalldisk can be removed from name - $NewName=($AzureImage.SKU).replace("-smalldisk","") - $galleryImageName=$NewName - $galleryImageSourcePath="c:\ClusterStorage\$LibraryVolumeName\$galleryImageName.vhdx" - $osType=$AzureImage.OSType - az azurestackhci galleryimage create --subscription $HCISubscriptionID --resource-group $HCIResourceGroupName --extended-location name="/subscriptions/$HCISubscriptionID/resourceGroups/$HCIResourceGroupName/providers/Microsoft.ExtendedLocation/customLocations/$CustomLocationName" type="CustomLocation" --location $ArcResourceBridgeLocation --image-path $galleryImageSourcePath --name $galleryImageName --os-type $osType - } - #endregion -#endregion - -#now you can navigate to https://aka.ms/AzureArcVM to create VMs! - -#region collect logs - #Enable CredSSP - # Temporarily enable CredSSP delegation to avoid double-hop issue - foreach ($ClusterNodeName in $ClusterNodeNames){ - Enable-WSManCredSSP -Role "Client" -DelegateComputer $ClusterNodeName -Force - } - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Enable-WSManCredSSP Server -Force } - - $SecureStringPassword = ConvertTo-SecureString $CredSSPPassword -AsPlainText -Force - $Credentials = New-Object System.Management.Automation.PSCredential ($CredSSPUserName, $SecureStringPassword) - - #generate logs - Invoke-Command -ComputerName $ClusterNodeNames[0] -Credential $Credentials -Authentication Credssp -ScriptBlock { - get-archcilogs - } - - # Disable CredSSP - Disable-WSManCredSSP -Role Client - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Disable-WSManCredSSP Server } - - #copy logs to downloads - $Session=New-PSSession -ComputerName $ClusterNodeNames[0] - Copy-Item -Path $env:userprofile\Documents\archcilogs.zip -Destination $env:userprofile\Downloads -FromSession $Sessions - $Session | Remove-PSSession -#endregion - -#region cleanup - - #unregister Azure Stack HCI - #Grab registration info - $RegistrationInfo=Invoke-Command -ComputerName $CLusterName -ScriptBlock {Get-AzureStackHCI} - $AzureResourceUri= $RegistrationInfo.AzureResourceUri - $HCIResourceGroupName=$AzureResourceUri.split("/")[4] - $HCISubscriptionID=$AzureResourceUri.split("/")[2] - #login to Azure - if (-not (Get-AzContext)){ - Login-AzAccount -UseDeviceAuthentication - } - $subscriptionID=(Get-AzContext).Subscription.id - $armTokenItemResource = "https://management.core.windows.net/" - $graphTokenItemResource = "https://graph.windows.net/" - $azContext = Get-AzContext - $authFactory = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory - $graphToken = $authFactory.Authenticate($azContext.Account, $azContext.Environment, $azContext.Tenant.Id, $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $graphTokenItemResource).AccessToken - $armToken = $authFactory.Authenticate($azContext.Account, $azContext.Environment, $azContext.Tenant.Id, $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $armTokenItemResource).AccessToken - $id = $azContext.Account.Id - UnRegister-AzStackHCI -SubscriptionID $subscriptionID -ComputerName $ClusterName -GraphAccessToken $graphToken -ArmAccessToken $armToken -AccountId $id -Confirm:0 - - #login to Azure - if (-not (Get-AzContext)){ - Login-AzAccount -UseDeviceAuthentication - } - - #remove virtual network - az azurestackhci virtualnetwork delete --subscription $HCISubscriptionID --resource-group $HCIResourceGroupName --name $vnetName --yes - - #remove gallery images - foreach ($AzureImage in $AzureImages){ - az azurestackhci galleryimage delete --subscription $HCISubscriptionID --resource-group $HCIResourceGroupName --name $AzureImage.SKU - } - - #remove custom location - az customlocation delete --resource-group $HCIResourceGroupName --name $customLocationName --yes - - #remove Kubernetes Extension - $BridgeResourceName=("$($RegistrationInfo.AzureResourceName)-arcbridge").ToLower() - az k8s-extension delete --cluster-type appliances --cluster-name $BridgeResourceName --resource-group $HCIResourceGroupName --name hci-vmoperator --yes - - #remove appliance - az arcappliance delete hci --config-file \\$ClusterName\clusterstorage$\MOC\workingDir\hci-appliance.yaml --yes - - #remove configfiles - Invoke-Command -ComputerName $ClusterName -ScriptBlock { - Remove-ArcHciConfigFiles - } - - #remove resource group - #login to Azure - if (-not (Get-AzContext)){ - Login-AzAccount -UseDeviceAuthentication - } - Remove-AzResourceGroup -Name $ResourceGroupName -Force - - #uninstall MOC - #Enable CredSSP - # Temporarily enable CredSSP delegation to avoid double-hop issue - foreach ($ClusterNodeName in $ClusterNodeNames){ - Enable-WSManCredSSP -Role "Client" -DelegateComputer $ClusterNodeName -Force - } - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Enable-WSManCredSSP Server -Force } - - $SecureStringPassword = ConvertTo-SecureString $CredSSPPassword -AsPlainText -Force - $Credentials = New-Object System.Management.Automation.PSCredential ($CredSSPUserName, $SecureStringPassword) - - #uninstall MOC - Invoke-Command -ComputerName $ClusterNodeNames[0] -Credential $Credentials -Authentication Credssp -ScriptBlock { - Uninstall-Moc - } - # Disable CredSSP - Disable-WSManCredSSP -Role Client - Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Disable-WSManCredSSP Server } - - #remove volume for MOC - Remove-VirtualDisk -FriendlyName $VolumeName -CimSession $ClusterName -Confirm:0 - - #remove volume for MOC - Remove-VirtualDisk -FriendlyName $LibraryVolumeName -CimSession $ClusterName -Confirm:0 - -#endregion \ No newline at end of file diff --git a/Scenarios/AzSHCI and Resource Bridge/LabConfig.ps1 b/Scenarios/AzSHCI and Resource Bridge/LabConfig.ps1 new file mode 100644 index 00000000..74d5bc74 --- /dev/null +++ b/Scenarios/AzSHCI and Resource Bridge/LabConfig.ps1 @@ -0,0 +1,14 @@ + +$LabConfig=@{ManagementSubnetIDs=0..1 ;DomainAdminName='LabAdmin'; AdminPassword='LS1setup!' ; <#Prefix = 'MSLab-'#> ; DCEdition='4'; Internet=$true ; TelemetryLevel='Full' ; TelemetryNickname='JaromiK' ; CustomDnsForwarders="10.8.8.8","10.7.7.7" ; AdditionalNetworksConfig=@(); VMs=@()} + +#2 nodes for AzSHCI Cluster +1..2 | ForEach-Object {$VMNames="ASHCIRB" ; $LABConfig.VMs += @{ VMName = "$VMNames$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI22H2_G2.vhdx' ; HDDNumber = 4 ; HDDSize= 4TB ; MemoryStartupBytes= 14GB; VMProcessorCount="Max" ; NestedVirt=$true ; VirtualTPM=$true}} + +#or small just when host is limited +#1..2 | ForEach-Object {$LABConfig.VMs += @{ VMName = "AzSHCI$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI21H2_G2.vhdx' ; HDDNumber = 10 ; HDDSize= 10TB ; MemoryStartupBytes= 1GB; VMProcessorCount="Max" ; vTPM=$true}} + +#Optional Windows Admin Center in GW mode +$LabConfig.VMs += @{ VMName = 'WACGW' ; ParentVHD = 'Win2022Core_G2.vhdx'; MGMTNICs=1} + +#Management machine +$LabConfig.VMs += @{ VMName = 'Management' ; ParentVHD = 'Win2022_G2.vhdx' ; MGMTNICs=1 } \ No newline at end of file diff --git a/Scenarios/AzSHCI and Resource Bridge/Scenario.ps1 b/Scenarios/AzSHCI and Resource Bridge/Scenario.ps1 new file mode 100644 index 00000000..966d8cb0 --- /dev/null +++ b/Scenarios/AzSHCI and Resource Bridge/Scenario.ps1 @@ -0,0 +1,487 @@ +#https://learn.microsoft.com/en-us/azure/aks/hybrid/deploy-aks-service-hci?tabs=powershell#step-2-install-the-aks-hybrid-extension-on-the-azure-arc-resource-bridge +#https://aka.ms/ArcEnabledHCI + +#region Variables +$ClusterNodeNames="ASHCIRB1","ASHCIRB2" +$ClusterName="ASHCIRB-Cluster" +$vswitchName="vSwitch" +$controlPlaneIP="10.0.0.101" +$VolumeName="MOC" +$VolumePath="c:\ClusterStorage\$VolumeName" +$CredSSPUserName="CORP\LabAdmin" +$CredSSPPassword="LS1setup!" +$CustomLocationName="$ClusterName" +$CustomLocationNameSpace="customlocation-ns" + +#ARC VMs virtual network name (the one that is visible in portal when you create a VM) +$vnetName="management" + +#AKS config +$AKSvnetName="AKSvnet" +$VIPPoolStart="10.0.1.2" +$VIPPoolEnd="10.0.1.50" +$DHCPServer="DC" +$DHCPScopeID="10.0.1.0" +$VLANID=11 + +#for static aks deployment +$IPAddressPrefix="10.0.1.0/24" +$Gateway="10.0.1.1" +$dnsservers="10.0.1.1" +$k8snodeippoolstart="10.0.1.51" +$k8snodeippoolend="10.0.1.254" + +#if you want custom images to add +$LibraryVolumeName="Library" #volume for Gallery images for VMs +$AzureImages=@() +$AzureImages+=@{PublisherName = "microsoftwindowsserver";Offer="windowsserver";SKU="2022-datacenter-azure-edition-smalldisk";OSType="Windows"} #OS TYpe can be "Windows" or "Linux" - first letter has to be capital! +$AzureImages+=@{PublisherName = "microsoftwindowsserver";Offer="windowsserver";SKU="2022-datacenter-azure-edition-core-smalldisk";OSType="Windows"} #OS TYpe can be "Windows" or "Linux" - first letter has to be capital! + + +#Install or update Azure packages +Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force +$ModuleNames="Az.Accounts","Az.Compute","Az.Resources","Az.StackHCI" +foreach ($ModuleName in $ModuleNames){ + $Module=Get-InstalledModule -Name $ModuleName -ErrorAction Ignore + if ($Module){$LatestVersion=(Find-Module -Name $ModuleName).Version} + if (-not($Module) -or ($Module.Version -lt $LatestVersion)){ + Install-Module -Name $ModuleName -Force + } +} + +#login to Azure +if (-not (Get-AzContext)){ + Login-AzAccount -UseDeviceAuthentication +} + +$ResourceGroupName="$ClusterName-rg" +$SubscriptionID=(Get-AzContext).Subscription.ID + +#select context +$context=Get-AzContext -ListAvailable +if (($context).count -gt 1){ + $context=$context | Out-GridView -OutputMode Single + $context | Set-AzContext +} + +#I did only test same for all (EastUS) +$VMImageLocation="eastus" +$ArcResourceBridgeLocation="eastus" +$AzureStackLocation="eastus" + +<#or populate by choosing your own +#grab region where to grab VMs from +$VMImageLocation = (Get-AzLocation | Where-Object Providers -Contains "Microsoft.Compute" | Out-GridView -OutputMode Single -Title "Choose location where to grab VMs from").Location + +#grab location for Arc Resource Bridge and Custom location +$ArcResourceBridgeLocation=(Get-AzLocation | Where-Object Providers -Contains "Microsoft.ResourceConnector" | Out-GridView -OutputMode Single -Title "Choose location for Arc Resource Bridge and Custom location").Location + +#grab location for Azure Stack +$AzureStackLocation=(Get-AzResourceProvider -ProviderNamespace Microsoft.AzureStackHCI).Where{($_.ResourceTypes.ResourceTypeName -eq 'clusters' -and $_.RegistrationState -eq 'Registered')}.Locations | Out-GridView -OutputMode Single -Title "Please select Location for AzureStackHCI metadata" +$AzureStackLocation = $region -replace '\s','' +$AzureStackLocation = $region.ToLower() +#> + +#endregion + +#region Create 2 node cluster (just simple. Not for prod - follow hyperconverged scenario for real clusters https://github.com/microsoft/MSLab/tree/master/Scenarios/AzSHCI%20Deployment%2022H2%20Edition) +# Install features for management on server +Install-WindowsFeature -Name RSAT-Clustering,RSAT-Clustering-Mgmt,RSAT-Clustering-PowerShell,RSAT-Hyper-V-Tools + +# Update servers (optional) + Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { + New-PSSessionConfigurationFile -RunAsVirtualAccount -Path $env:TEMP\VirtualAccount.pssc + Register-PSSessionConfiguration -Name 'VirtualAccount' -Path $env:TEMP\VirtualAccount.pssc -Force + } -ErrorAction Ignore + # Run Windows Update via ComObject. + Invoke-Command -ComputerName $ClusterNodeNames -ConfigurationName 'VirtualAccount' { + $Searcher = New-Object -ComObject Microsoft.Update.Searcher + $SearchCriteriaAllUpdates = "IsInstalled=0 and DeploymentAction='Installation' or + IsPresent=1 and DeploymentAction='Uninstallation' or + IsInstalled=1 and DeploymentAction='Installation' and RebootRequired=1 or + IsInstalled=0 and DeploymentAction='Uninstallation' and RebootRequired=1" + $SearchResult = $Searcher.Search($SearchCriteriaAllUpdates).Updates + $Session = New-Object -ComObject Microsoft.Update.Session + $Downloader = $Session.CreateUpdateDownloader() + $Downloader.Updates = $SearchResult + $Downloader.Download() + $Installer = New-Object -ComObject Microsoft.Update.Installer + $Installer.Updates = $SearchResult + $Result = $Installer.Install() + $Result + } + #remove temporary PSsession config + Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { + Unregister-PSSessionConfiguration -Name 'VirtualAccount' + Remove-Item -Path $env:TEMP\VirtualAccount.pssc + } + +# Install features on servers +Invoke-Command -computername $ClusterNodeNames -ScriptBlock { + Enable-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V -Online -NoRestart + Install-WindowsFeature -Name "Failover-Clustering","RSAT-Clustering-Powershell","Hyper-V-PowerShell" +} + +# restart servers +Restart-Computer -ComputerName $ClusterNodeNames -Protocol WSMan -Wait -For PowerShell +#failsafe - sometimes it evaluates, that servers completed restart after first restart (hyper-v needs 2) +Start-sleep 20 + +# create vSwitch (sometimes happens, that I need to restart servers again and then it will create vSwitch...) +Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock {New-VMSwitch -Name $using:vswitchName -EnableEmbeddedTeaming $TRUE -NetAdapterName (Get-NetIPAddress -IPAddress 10.* ).InterfaceAlias} + +#create cluster +New-Cluster -Name $ClusterName -Node $ClusterNodeNames +Start-Sleep 5 +Clear-DNSClientCache + +#add file share witness +#Create new directory + $WitnessName=$ClusterName+"Witness" + Invoke-Command -ComputerName DC -ScriptBlock {new-item -Path c:\Shares -Name $using:WitnessName -ItemType Directory} + $accounts=@() + $accounts+="corp\$($ClusterName)$" + $accounts+="corp\Domain Admins" + New-SmbShare -Name $WitnessName -Path "c:\Shares\$WitnessName" -FullAccess $accounts -CimSession DC +#Set NTFS permissions + Invoke-Command -ComputerName DC -ScriptBlock {(Get-SmbShare $using:WitnessName).PresetPathAcl | Set-Acl} +#Set Quorum + Set-ClusterQuorum -Cluster $ClusterName -FileShareWitness "\\DC\$WitnessName" + +#Enable S2D +Enable-ClusterS2D -CimSession $ClusterName -Verbose -Confirm:0 + +#configure thin volumes a default if available (because why not :) +$OSInfo=Invoke-Command -ComputerName $ClusterName -ScriptBlock { + Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\' +} +if ($OSInfo.productname -eq "Azure Stack HCI" -and $OSInfo.CurrentBuild -ge 20348){ + Get-StoragePool -CimSession $ClusterName -FriendlyName S2D* | Set-StoragePool -ProvisioningTypeDefault Thin +} + + +#endregion + +#region Register Azure Stack HCI to Azure - if not registered, VMs are not added as cluster resources = AKS script will fail +#download Azure module +Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force +if (!(Get-InstalledModule -Name Az.StackHCI -ErrorAction Ignore)){ + Install-Module -Name Az.StackHCI -Force +} + +#login to azure +#download Azure module +if (!(Get-InstalledModule -Name az.accounts -ErrorAction Ignore)){ + Install-Module -Name Az.Accounts -Force +} +Connect-AzAccount -UseDeviceAuthentication + +#select subscription if more available +$subscription=Get-AzSubscription +if (($subscription).count -gt 1){ + $subscription | Out-GridView -OutputMode Single | Set-AzContext +} + +#grab subscription ID +$subscriptionID=(Get-AzContext).Subscription.id + +<# Register AZSHCi without prompting for creds, +Notes: As Dec. 2021, in Azure Stack HCI 21H2, if you Register-AzStackHCI the cluster multiple times in same ResourceGroup (e.g. default +resource group name is AzSHCI-Cluster-rg) without run UnRegister-AzStackHCI first, although you may succeed in cluster registration, but +sever node Arc integration will fail, even if you have deleted the ResourceGroup in Azure Portal before running Register-AzStackHCI #> + +$armTokenItemResource = "https://management.core.windows.net/" +$graphTokenItemResource = "https://graph.windows.net/" +$azContext = Get-AzContext +$authFactory = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory +$armToken = $authFactory.Authenticate($azContext.Account, $azContext.Environment, $azContext.Tenant.Id, $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $armTokenItemResource).AccessToken +$id = $azContext.Account.Id + +#register +Register-AzStackHCI -SubscriptionID $subscriptionID -Region $AzureStackLocation -ComputerName $ClusterName -ArmAccessToken $armToken -AccountId $id + +#Install Azure Stack HCI RSAT Tools to all nodes +Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { + Install-WindowsFeature -Name RSAT-Azure-Stack-HCI +} +#Validate registration (query on just one node is needed) +Invoke-Command -ComputerName $ClusterName -ScriptBlock { + Get-AzureStackHCI +} +#endregion + +#region Install modules and create MOC agent Service + #Install required modules + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force + Install-Module -Name PowershellGet -Force -Confirm:$false -SkipPublisherCheck + Update-Module -Name PowerShellGet + + #to be able to install ArcHci and MOC, powershellget 2.2.5 needs to be used - to this posh restart is needed + Start-Process -Wait -FilePath PowerShell -ArgumentList { + Install-Module -Name MOC -Repository PSGallery -Force -AcceptLicense + Install-Module -Name ArcHci -Force -Confirm:$false -SkipPublisherCheck -AcceptLicense + } + + #Increase MaxEvenlope and create session to copy files to + Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock {Set-Item -Path WSMan:\localhost\MaxEnvelopeSizekb -Value 4096} + + #distribute modules to cluster nodes + $ModuleNames="ArcHci","Moc","DownloadSDK" + $PSSessions=New-PSSession -ComputerName $ClusterNodeNames + Foreach ($PSSession in $PSSessions){ + Foreach ($ModuleName in $ModuleNames){ + Copy-Item -Path $env:ProgramFiles\windowspowershell\modules\$ModuleName -Destination $env:ProgramFiles\windowspowershell\modules -ToSession $PSSession -Recurse -Force + } + Foreach ($ModuleName in $RequiredModules.ModuleName){ + Copy-Item -Path $env:ProgramFiles\windowspowershell\modules\$ModuleName -Destination $env:ProgramFiles\windowspowershell\modules -ToSession $PSSession -Recurse -Force + } + } + + #Enable CredSSP + # Temporarily enable CredSSP delegation to avoid double-hop issue + foreach ($ClusterNodeName in $ClusterNodeNames){ + Enable-WSManCredSSP -Role "Client" -DelegateComputer $ClusterNodeName -Force + } + Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Enable-WSManCredSSP Server -Force } + + $SecureStringPassword = ConvertTo-SecureString $CredSSPPassword -AsPlainText -Force + $Credentials = New-Object System.Management.Automation.PSCredential ($CredSSPUserName, $SecureStringPassword) + + #initialize MOC + Invoke-Command -ComputerName $ClusterNodeNames -Credential $Credentials -Authentication Credssp -ScriptBlock { + Initialize-MocNode + } + + #Create volume for MOC if does not exist + if (-not (Get-Volume -FriendlyName $VolumeName -CimSession $ClusterName -ErrorAction SilentlyContinue)) { + New-Volume -FriendlyName $VolumeName -CimSession $ClusterName -Size 1TB -StoragePoolFriendlyName S2D* + } + + #prepare arc resource bridge + #Configure MOC + Invoke-Command -ComputerName $ClusterNodeNames[0] -Credential $Credentials -Authentication Credssp -ScriptBlock { + Set-MocConfig -workingDir $using:VolumePath\workingDir -imageDir $using:VolumePath\imageStore -skipHostLimitChecks -cloudConfigLocation $using:VolumePath\cloudStore -catalog aks-hci-stable-catalogs-ext -ring stable -createAutoConfigContainers $false + } + + #Install MOC Cloud Agent Service + Invoke-Command -ComputerName $ClusterNodeNames[0] -Credential $Credentials -Authentication Credssp -ScriptBlock { + Install-moc + } + + # Disable CredSSP + Disable-WSManCredSSP -Role Client + Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Disable-WSManCredSSP Server } + +#endregion + +#region Create custom location and install Arc Resource Bridge + #Login to azure + if (!(Get-AzContext)){ + Connect-AzAccount -UseDeviceAuthentication + } + + #generate variables + #Grab registration info + $RegistrationInfo=Invoke-Command -ComputerName $CLusterName -ScriptBlock {Get-AzureStackHCI} + $AzureResourceUri= $RegistrationInfo.AzureResourceUri + $HCIResourceGroupName=$AzureResourceUri.split("/")[4] + $HCISubscriptionID=$AzureResourceUri.split("/")[2] + #create bridge resource name + $BridgeResourceName=("$($RegistrationInfo.AzureResourceName)-arcbridge").ToLower() + + #install Az CLI + #download + Start-BitsTransfer -Source https://aka.ms/installazurecliwindows -Destination $env:userprofile\Downloads\AzureCLI.msi + #install + Start-Process msiexec.exe -Wait -ArgumentList "/I $env:userprofile\Downloads\AzureCLI.msi /quiet" + #add az to enviromental variables so no posh restart is needed + [System.Environment]::SetEnvironmentVariable('PATH',$Env:PATH+';C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin') + + #add Az extensions + az extension add --name customlocation + az extension add --name azurestackhci + az extension add --name arcappliance + az extension add --name k8s-extension + az extension add --name connectedk8s + + #register namespaces + #register + $Providers="Microsoft.ExtendedLocation","Microsoft.ResourceConnector" + foreach ($Provider in $Providers){ + Register-AzResourceProvider -ProviderNamespace $Provider + } + #wait until resource providers are registered + foreach ($Provider in $Providers){ + do { + $Status=Get-AzResourceProvider -ProviderNamespace $Provider + Write-Output "Registration Status - $Provider : $(($status.RegistrationState -match 'Registered').Count)/$($Status.Count)" + Start-Sleep 1 + } while (($status.RegistrationState -match "Registered").Count -ne ($Status.Count)) + } + + #login with device authentication + az login --use-device-code + $allSubscriptions = (az account list | ConvertFrom-Json).ForEach({$_ | Select-Object -Property Name, id, tenantId }) + if (($allSubscriptions).Count -gt 1){ + $subscription = ($allSubscriptions | Out-GridView -OutputMode Single) + az account set --subscription $subscription.id + } + #create arc appliance + #generate config files + #Enable CredSSP + # Temporarily enable CredSSP delegation to avoid double-hop issue + foreach ($ClusterNodeName in $ClusterNodeNames){ + Enable-WSManCredSSP -Role "Client" -DelegateComputer $ClusterNodeName -Force + } + Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Enable-WSManCredSSP Server -Force } + + $SecureStringPassword = ConvertTo-SecureString $CredSSPPassword -AsPlainText -Force + $Credentials = New-Object System.Management.Automation.PSCredential ($CredSSPUserName, $SecureStringPassword) + + Invoke-Command -ComputerName $ClusterNodeNames[0] -Credential $Credentials -Authentication Credssp -ScriptBlock { + New-ArcHciConfigFiles -subscriptionID $using:HCISubscriptionID -location $using:ArcResourceBridgeLocation -resourceGroup $using:HCIResourceGroupName -resourceName $using:BridgeResourceName -workDirectory "\\$using:ClusterName\ClusterStorage$\$using:VolumeName\workingDir" -controlPlaneIP $using:controlPlaneIP -vipPoolStart $using:controlPlaneIP -vipPoolEnd $using:controlPlaneIP -vswitchName $using:vswitchName #-vLanID $vlanID + } + # Disable CredSSP + Disable-WSManCredSSP -Role Client + Invoke-Command -ComputerName $ClusterNodeNames -ScriptBlock { Disable-WSManCredSSP Server } + + #prepare + az arcappliance prepare hci --config-file \\$ClusterName\ClusterStorage$\$VolumeName\workingDir\hci-appliance.yaml + + #Create folder for config + New-Item -Path $env:USERPROFILE\.kube -ItemType Directory -ErrorAction Ignore + + #deploy control plane and export kube config + az arcappliance deploy hci --config-file \\$ClusterName\ClusterStorage$\$VolumeName\workingDir\hci-appliance.yaml --outfile $env:USERPROFILE\.kube\config + #create connection to Azure (might throw error, dont worry! It's being deployed on background) + az arcappliance create hci --config-file \\$ClusterName\ClusterStorage$\$VolumeName\workingDir\hci-appliance.yaml --kubeconfig $env:USERPROFILE\.kube\config + + #wait until appliance is running + do { + $Status=az arcappliance show --only-show-errors --resource-group $HCIResourceGroupName --name $BridgeResourceName | ConvertFrom-Json + Write-Host -NoNewline -Object "." + Start-Sleep 2 + } until ($status.status -match "Running") + + #verify if appliance is running + az arcappliance show --resource-group $HCIResourceGroupName --name $BridgeResourceName | ConvertFrom-Json + + #Add hci-vmoperator extension + #create + az k8s-extension create --cluster-type appliances --cluster-name $BridgeResourceName --resource-group $HCIResourceGroupName --name hci-vmoperator --extension-type Microsoft.AZStackHCI.Operator --scope cluster --release-namespace helm-operator2 --configuration-settings Microsoft.CustomLocation.ServiceAccount=hci-vmoperator --config-protected-file \\$ClusterName\ClusterStorage$\$VolumeName\workingDir\hci-config.json --configuration-settings HCIClusterID=$AzureResourceUri --auto-upgrade true + #validate + az k8s-extension show --cluster-type appliances --cluster-name $BridgeResourceName --resource-group $HCIResourceGroupName --name hci-vmoperator + + #Create custom location (has to be created after arcappliance deployment) + az customlocation create --resource-group $HCIResourceGroupName --name $CustomLocationName --cluster-extension-ids "/subscriptions/$HCISubscriptionID/resourceGroups/$HCIResourceGroupName/providers/Microsoft.ResourceConnector/appliances/$BridgeResourceName/providers/Microsoft.KubernetesConfiguration/extensions/hci-vmoperator" --namespace $CustomLocationNameSpace --host-resource-id "/subscriptions/$HCISubscriptionID/resourceGroups/$HCIResourceGroupName/providers/Microsoft.ResourceConnector/appliances/$BridgeResourceName" --location $ArcResourceBridgeLocation + + <# Or with PowerShell + #install Az.CustomLocation module + if (!(Get-InstalledModule -Name az.CustomLocation -ErrorAction Ignore)){ + Install-Module -Name Az.CustomLocation -Force + } + New-AzCustomLocation -ResourceGroupName $ResourceGroupName -Name $CustomLocationName -ClusterExtensionID "/subscriptions/$SubscriptionID/resourceGroups/$ResourceGroupName/providers/Microsoft.ResourceConnector/appliances/$BridgeResourceName/providers/Microsoft.KubernetesConfiguration/extensions/hci-vmoperator" -NameSpace hci-vmoperator -HostResourceID "/subscriptions/$SubscriptionID/resourceGroups/$ResourceGroupName/providers/Microsoft.ResourceConnector/appliances/$BridgeResourceName" -Location $ArcResourceBridgeLocation + #> + +#endregion + +#region create virtual network for arcVMs + #Grab registration info + $RegistrationInfo=Invoke-Command -ComputerName $CLusterName -ScriptBlock {Get-AzureStackHCI} + $AzureResourceUri= $RegistrationInfo.AzureResourceUri + $HCIResourceGroupName=$AzureResourceUri.split("/")[4] + $HCISubscriptionID=$AzureResourceUri.split("/")[2] + #create network + az azurestackhci virtualnetwork create --subscription $HCISubscriptionID --resource-group $HCIResourceGroupName --extended-location name="/subscriptions/$HCISubscriptionID/resourceGroups/$HCIResourceGroupName/providers/Microsoft.ExtendedLocation/customLocations/$CustomLocationName" type="CustomLocation" --location $ArcResourceBridgeLocation --network-type "Transparent" --name $vnetName +#endregion + +#region Copy kube config to nodes to have it available there +$Sessions=New-PSSession -ComputerName $ClusterNodeNames + +#copy kube to cluster nodes +Foreach ($Session in $Sessions){ + Copy-Item -Path "$env:userprofile\.kube" -Destination $env:userprofile -ToSession $Session -Recurse -Force +} + +$Sessions | Remove-PSSession +#endregion + +#region add aks hybrid extension to the custom location +#https://learn.microsoft.com/en-us/azure/aks/hybrid/deploy-aks-service-hci?tabs=powershell#step-2-install-the-aks-hybrid-extension-on-the-azure-arc-resource-bridge + + #add extension + $aksHybridExtnName = "aks-hybrid-extn" + az k8s-extension create --resource-group $HCIResourceGroupName --cluster-name $BridgeResourceName --cluster-type appliances --name $aksHybridExtnName --extension-type Microsoft.HybridAKSOperator --config Microsoft.CustomLocation.ServiceAccount=$CustomLocationNameSpace + + #Patch your existing custom location to support AKS hybrid alongside Arc VMs + $ArcResourceBridgeId=az arcappliance show -g $HCIResourceGroupName --name $BridgeResourceName --query id -o tsv + $VMClusterExtensionResourceId=az k8s-extension list -g $HCIResourceGroupName --cluster-name $BridgeResourceName --cluster-type appliances --query "[?extensionType == ``microsoft.azstackhci.operator``].id" -o tsv + $AKSClusterExtensionResourceId=az k8s-extension show -g $HCIResourceGroupName --cluster-name $BridgeResourceName --cluster-type appliances --name $aksHybridExtnName --query id -o tsv + az customlocation patch --name $customLocationName --namespace $CustomLocationNameSpace --host-resource-id $ArcResourceBridgeId --cluster-extension-ids $VMClusterExtensionResourceId $AKSClusterExtensionResourceId --resource-group $HCIResourceGroupName + + #check + az customlocation show --name $customLocationName --resource-group $HCIResourceGroupName --query "clusterExtensionIds" -o tsv +#endregion + +#region create virtual network for AKS +#https://learn.microsoft.com/en-us/azure/aks/hybrid/create-aks-hybrid-preview-networks?tabs=dhcp%2Clinux-vhd + + #make sure latest module is installed (note required version) + Start-Process -Wait -FilePath PowerShell -ArgumentList { + Install-Module -Name MOC -Repository PSGallery -Force -AcceptLicense + Install-Module -Name ArcHci -Force -Confirm:$false -SkipPublisherCheck -AcceptLicense -RequiredVersion 0.2.24 + } + + #distribute new module to cluster nodes + $ModuleNames="ArcHci" + $PSSessions=New-PSSession -ComputerName $ClusterNodeNames + Foreach ($PSSession in $PSSessions){ + Foreach ($ModuleName in $ModuleNames){ + Copy-Item -Path $env:ProgramFiles\windowspowershell\modules\$ModuleName -Destination $env:ProgramFiles\windowspowershell\modules -ToSession $PSSession -Recurse -Force + } + Foreach ($ModuleName in $RequiredModules.ModuleName){ + Copy-Item -Path $env:ProgramFiles\windowspowershell\modules\$ModuleName -Destination $env:ProgramFiles\windowspowershell\modules -ToSession $PSSession -Recurse -Force + } + } + + #since there was a subnet configured for AKS (note the labconfig), let's exclude VIP pool from dhcp + #make sure dhcp tools are installed + install-windowsfeature -name RSAT-DHCP + #exclude + Add-DhcpServerv4ExclusionRange -StartRange $VIPPoolStart -EndRange $VIPPoolEnd -ScopeId $DHCPScopeID -ComputerName $DHCPServer + + # + Invoke-Command -ComputerName $ClusterName -ScriptBlock { + #dhcp does not work as it keeps asking for gw, dns servers... + #New-ArcHciVirtualNetwork -name AKSvnet -vswitchname $using:vswitchname -vippoolstart $using:vipPoolStart -vippoolend $using:vipPoolEnd -vlanid $using:vlanid + #without dhcp + New-ArcHciVirtualNetwork -name $using:AKSVnetName -vswitchname $using:vswitchname -vippoolstart $using:vipPoolStart -vippoolend $using:vipPoolEnd -vlanid $using:vlanid -ipAddressPrefix $Using:ipaddressprefix -gateway $using:gateway -dnsservers $using:DNSServers -k8sNodeIpPoolStart $using:k8sNodeIpPoolStart -k8sNodeIpPoolEnd $using:k8sNodeIpPoolend + } + + #Connect your on-premises AKS hybrid network to Azure + az extension add --name hybridaks + #register namespace provider + $Providers="Microsoft.HybridContainerService" + foreach ($Provider in $Providers){ + Register-AzResourceProvider -ProviderNamespace $Provider + } + #wait until resource providers are registered + foreach ($Provider in $Providers){ + do { + $Status=Get-AzResourceProvider -ProviderNamespace $Provider + Write-Output "Registration Status - $Provider : $(($status.RegistrationState -match 'Registered').Count)/$($Status.Count)" + Start-Sleep 1 + } while (($status.RegistrationState -match "Registered").Count -ne ($Status.Count)) + } + + #add network + az hybridaks vnet create -n $AKSVnetName -g $HCIResourceGroupName --custom-location $customLocationName --moc-vnet-name $AKSVnetName + +#endregion + +#region add image for aks +Invoke-Command -ComputerName $ClusterName -ScriptBlock { + Add-ArcHciK8sGalleryImage -k8sVersion 1.22.11 -version 1.0.16.10113 +} +#endregion + From 227d5895f0f24fcd51119a0d10fe1130a71ad051 Mon Sep 17 00:00:00 2001 From: Jaromirm Date: Sat, 29 Jul 2023 23:44:03 +0200 Subject: [PATCH 3/5] updated for latest release --- .../AzSHCI and Deployment tool/LabConfig.ps1 | 4 +- .../AzSHCI and Deployment tool/Scenario.ps1 | 283 +++++++++++------- 2 files changed, 184 insertions(+), 103 deletions(-) diff --git a/Scenarios/AzSHCI and Deployment tool/LabConfig.ps1 b/Scenarios/AzSHCI and Deployment tool/LabConfig.ps1 index f38a674b..bfae3de7 100644 --- a/Scenarios/AzSHCI and Deployment tool/LabConfig.ps1 +++ b/Scenarios/AzSHCI and Deployment tool/LabConfig.ps1 @@ -2,9 +2,9 @@ $LabConfig=@{AllowedVLANs="1-10,711-719" ; DomainAdminName='LabAdmin'; AdminPass #Azure Stack HCI 22H2 #labconfig will not domain join VMs, will add "Tools disk" and will also execute powershell command to make this tools disk online. -1..4 | ForEach-Object {$LABConfig.VMs += @{ VMName = "ASNode$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI22H2_G2.vhdx' ; HDDNumber = 4 ; HDDSize= 2TB ; MemoryStartupBytes= 1GB; VMProcessorCount=4 ; vTPM=$true ; AddToolsVHD=$True ; Unattend="NoDjoin" }} +1..4 | ForEach-Object {$LABConfig.VMs += @{ VMName = "ASNode$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI23H2_G2.vhdx' ; HDDNumber = 4 ; HDDSize= 2TB ; MemoryStartupBytes= 1GB; VMProcessorCount=4 ; vTPM=$true ; Unattend="NoDjoin" }} #labconfig for nested virtualization -#1..4 | ForEach-Object {$LABConfig.VMs += @{ VMName = "ASNode$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI22H2_G2.vhdx' ; HDDNumber = 4 ; HDDSize= 2TB ; MemoryStartupBytes= 6GB; VMProcessorCount=4 ; vTPM=$true ; AddToolsVHD=$True ; Unattend="NoDjoin" ; NestedVirt=$true }} +#1..4 | ForEach-Object {$LABConfig.VMs += @{ VMName = "ASNode$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI23H2_G2.vhdx' ; HDDNumber = 4 ; HDDSize= 2TB ; MemoryStartupBytes= 6GB; VMProcessorCount=4 ; vTPM=$true ; Unattend="NoDjoin" ; NestedVirt=$true }} #Windows Admin Center in GW mode $LabConfig.VMs += @{ VMName = 'WACGW' ; ParentVHD = 'Win2022Core_G2.vhdx'; MGMTNICs=1} diff --git a/Scenarios/AzSHCI and Deployment tool/Scenario.ps1 b/Scenarios/AzSHCI and Deployment tool/Scenario.ps1 index 6aca980f..1b446715 100644 --- a/Scenarios/AzSHCI and Deployment tool/Scenario.ps1 +++ b/Scenarios/AzSHCI and Deployment tool/Scenario.ps1 @@ -23,77 +23,75 @@ Install-WindowsFeature -Name RSAT-AD-PowerShell,GPMC #populate objects - New-HciAdObjectsPreCreation -Deploy -AsHciDeploymentUserCredential $Credentials -AsHciOUName $AsHCIOUName -AsHciPhysicalNodeList $Servers -DomainFQDN $DomainFQDN -AsHciClusterName $ClusterName -AsHciDeploymentPrefix $Prefix + New-HciAdObjectsPreCreation -Deploy -AzureStackLCMUserCredential $Credentials -AsHciOUName $AsHCIOUName -AsHciPhysicalNodeList $Servers -DomainFQDN $DomainFQDN -AsHciClusterName $ClusterName -AsHciDeploymentPrefix $Prefix #install management features to explore cluster,settings... Install-WindowsFeature -Name "RSAT-ADDS","RSAT-Clustering" #endregion -#region Deploy - run from ASNode1! - #make D drives online - $Servers="ASNode1","ASNode2","ASNode3","ASNode4" - #add $Servers into trustedhosts - Set-Item WSMan:\localhost\Client\TrustedHosts -Value $($Servers -join ',') -Force - #invoke command - Invoke-Command -ComputerName $Servers -ScriptBlock { - get-disk -Number 1 | Set-Disk -IsReadOnly $false - get-disk -Number 1 | Set-Disk -IsOffline $false - } - - #Download files - $downloadfolder="D:" - $files=@() - $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2210545" ; FileName="BootstrapCloudDeploymentTool.ps1" ; Description="Bootstrap PowerShell"} - $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2210546" ; FileName="CloudDeployment_10.2303.0.36.zip" ; Description="Cloud Deployment Package"} - $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2210608" ; FileName="Verify-CloudDeployment.zip_Hash.ps1" ; Description="Verify Cloud Deployment PowerShell"} - - foreach ($file in $files){ - if (-not (Test-Path "$downloadfolder\$($file.filename)")){ - Start-BitsTransfer -Source $file.uri -Destination "$downloadfolder\$($file.filename)" -DisplayName "Downloading: $($file.filename)" - } - } +#region prepare azure prerequisites - run from ASNode1 or Management (you can run from Management, just copy the resulting variables and use it in next region) + #variables + $StorageAccountName="asclus01$(Get-Random -Minimum 100000 -Maximum 999999)" + $ServicePrincipal=$True #or false if you want to use MFA (and skip SP creation) + $ServicePrincipalName="Azure-Stack-Registration" + $ResourceGroupName="ASClus01-RG" + $Location="EastUS" - #Start bootstrap (script is looking for file "CloudDeployment_*.zip" - & D:\BootstrapCloudDeploymentTool.ps1 + #login to azure + #download Azure module + if (!(Get-InstalledModule -Name az.accounts -ErrorAction Ignore)){ + Install-Module -Name Az.Accounts -Force + } + if (-not (Get-AzContext)){ + Connect-AzAccount -UseDeviceAuthentication + } - #create deployment credentials - $UserName="ASClus01-DeployUser" - $Password="LS1setup!" - $SecuredPassword = ConvertTo-SecureString $password -AsPlainText -Force - $AzureStackLCMUserCredential = New-Object System.Management.Automation.PSCredential ($UserName,$SecuredPassword) + #select subscription if more available + $subscriptions=Get-AzSubscription + #list subscriptions + $subscriptions + if (($subscriptions).count -gt 1){ + $SubscriptionID=Read-Host "Please give me subscription ID" + }else{ + $SubscriptionID=$subscriptions.id + } - $UserName="Administrator" - $Password="LS1setup!" - $SecuredPassword = ConvertTo-SecureString $password -AsPlainText -Force - $LocalAdminCred = New-Object System.Management.Automation.PSCredential ($UserName,$SecuredPassword) - $CloudName="AzureCloud" - $ServicePrincipalName="Azure-Stack-Registration" - - #login to azure - #download Azure module - if (!(Get-InstalledModule -Name az.accounts -ErrorAction Ignore)){ - Install-Module -Name Az.Accounts -Force - } - if (-not (Get-AzContext)){ - Connect-AzAccount -UseDeviceAuthentication - } - #select subscription if more available - $subscriptions=Get-AzSubscription - #list subscriptions - $subscriptions - if (($subscriptions).count -gt 1){ - $SubscriptionID=Read-Host "Please give me subscription ID" - }else{ - $SubscriptionID=$subscriptions.id - } + #make sure resource providers are registered + if (!(Get-InstalledModule -Name "az.resources" -ErrorAction Ignore)){ + Install-Module -Name "az.resources" -Force + } + $Providers="Microsoft.ResourceConnector","Microsoft.Authorization","Microsoft.AzureStackHCI","Microsoft.HybridCompute","Microsoft.GuestConfiguration" + foreach ($Provider in $Providers){ + Register-AzResourceProvider -ProviderNamespace $Provider + #wait for provider to finish registration + do { + $Status=Get-AzResourceProvider -ProviderNamespace $Provider + Write-Output "Registration Status - $Provider : $(($status.RegistrationState -match 'Registered').Count)/$($Status.Count)" + Start-Sleep 1 + } while (($status.RegistrationState -match "Registered").Count -ne ($Status.Count)) + } + + #Create Storage Account + if (!(Get-InstalledModule -Name "az.storage"-ErrorAction Ignore)){ + Install-Module -Name "az.storage" -Force + } - if (!(Get-InstalledModule -Name az.Resources -ErrorAction Ignore)){ - Install-Module -Name Az.Resources -Force - } + #create resource group first + if (-not(Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction Ignore)){ + New-AzResourceGroup -Name $ResourceGroupName -Location $location + } + #create Storage Account + If (-not(Get-AzStorageAccountKey -Name $StorageAccountName -ResourceGroupName $ResourceGroupName -ErrorAction Ignore)){ + New-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName -SkuName Standard_LRS -Location $location -Kind StorageV2 -AccessTier Cool + } + $StorageAccountAccessKey=(Get-AzStorageAccountKey -Name $StorageAccountName -ResourceGroupName $ResourceGroupName | Select-Object -First 1).Value - #Create Azure Stack HCI registration role https://learn.microsoft.com/en-us/azure-stack/hci/deploy/register-with-azure#assign-permissions-from-azure-portal - if (-not (Get-AzRoleDefinition -Name "Azure Stack HCI registration role - Custom")){ - $Content=@" + #create service principal if requested + if ($ServicePrincipal){ + #Create Azure Stack HCI registration role https://learn.microsoft.com/en-us/azure-stack/hci/deploy/register-with-azure#assign-permissions-from-azure-portal + #https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#azure-connected-machine-onboarding + if (-not (Get-AzRoleDefinition -Name "Azure Stack HCI registration role - Custom" -ErrorAction Ignore)){ + $Content=@" { "Name": "Azure Stack HCI registration role - Custom", "Id": null, @@ -110,7 +108,13 @@ "Microsoft.Authorization/roleAssignments/read", "Microsoft.HybridCompute/register/action", "Microsoft.GuestConfiguration/register/action", - "Microsoft.HybridConnectivity/register/action" + "Microsoft.HybridConnectivity/register/action", + "Microsoft.HybridCompute/machines/extensions/write", + "Microsoft.HybridCompute/machines/extensions/read", + "Microsoft.HybridCompute/machines/read", + "Microsoft.HybridCompute/machines/write", + "Microsoft.HybridCompute/privateLinkScopes/read", + "Microsoft.GuestConfiguration/guestConfigurationAssignments/read" ], "NotActions": [ ], @@ -119,50 +123,119 @@ ] } "@ - $Content | Out-File "$env:USERPROFILE\Downloads\customHCIRole.json" - New-AzRoleDefinition -InputFile "$env:USERPROFILE\Downloads\customHCIRole.json" - } - #Create AzADServicePrincipal for Azure Stack HCI registration - $SP=Get-AZADServicePrincipal -DisplayName $ServicePrincipalName - if (-not $SP){ - $SP=New-AzADServicePrincipal -DisplayName $ServicePrincipalName -Role "Azure Stack HCI registration role - Custom" - #remove default cred - Remove-AzADAppCredential -ApplicationId $SP.AppId - } + $Content | Out-File "$env:USERPROFILE\Downloads\customHCIRole.json" + New-AzRoleDefinition -InputFile "$env:USERPROFILE\Downloads\customHCIRole.json" + } + + #Create AzADServicePrincipal for Azure Stack HCI registration (if it does not exist) + $SP=Get-AZADServicePrincipal -DisplayName $ServicePrincipalName + if (-not $SP){ + $SP=New-AzADServicePrincipal -DisplayName $ServicePrincipalName -Role "Azure Stack HCI registration role - Custom" + #remove default cred + Remove-AzADAppCredential -ApplicationId $SP.AppId + } - #Create new SPN password - $credential = New-Object -TypeName "Microsoft.Azure.PowerShell.Cmdlets.Resources.MSGraph.Models.ApiV10.MicrosoftGraphPasswordCredential" -Property @{ - "KeyID" = (new-guid).Guid ; - "EndDateTime" = [DateTime]::UtcNow.AddYears(10) + #Create new SPN password + $credential = New-Object -TypeName "Microsoft.Azure.PowerShell.Cmdlets.Resources.MSGraph.Models.ApiV10.MicrosoftGraphPasswordCredential" -Property @{ + "KeyID" = (new-guid).Guid ; + "EndDateTime" = [DateTime]::UtcNow.AddYears(1) + } + $Creds=New-AzADAppCredential -PasswordCredentials $credential -ApplicationID $SP.AppID + $SPNSecret=$Creds.SecretText + $SPAppID=$SP.AppID + } + + Disconnect-AzAccount + #output variables + Write-Host -ForegroundColor Cyan @" + #Variables to copy + `$SubscriptionID=`"$SubscriptionID`" + `$SPAppID=`"$SPAppID`" + `$SPNSecret=`"$SPNSecret`" + `$ResourceGroupName=`"$ResourceGroupName`" + `$StorageAccountName=`"$StorageAccountName`" + `$StorageAccountAccessKey=`"$StorageAccountAccessKey`" + `$Location=`"$Location`" +"@ + + +#endregion + +#region Deploy - run from ASNode1! + #variables + #create deployment credentials + $UserName="ASClus01-DeployUser" + $Password="LS1setup!" + $SecuredPassword = ConvertTo-SecureString $password -AsPlainText -Force + $AzureStackLCMUserCredential = New-Object System.Management.Automation.PSCredential ($UserName,$SecuredPassword) + $UserName="Administrator" + $Password="LS1setup!" + $SecuredPassword = ConvertTo-SecureString $password -AsPlainText -Force + $LocalAdminCred = New-Object System.Management.Automation.PSCredential ($UserName,$SecuredPassword) + + #the one you have to populate if you did not run above region from Seed node + <# + $SubscriptionID="" + $SPAppID="" #not needed if you use MFA + $SPNSecret="" #not needed if you use MFA + $ResourceGroupName="" + $StorageAccountName="" + $StorageAccountAccessKey="" + $Location="" + #> + + #download folder + $downloadfolder="c:\temp" + + $Servers="ASNode1","ASNode2","ASNode3","ASNode4" + + #Download files + #create folder + if (-not (Test-Path $downloadfolder)){New-Item -Path $downloadfolder -ItemType Directory} + $files=@() + $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2210545" ; FileName="BootstrapCloudDeploymentTool.ps1" ; Description="Bootstrap PowerShell"} + $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2210546" ; FileName="CloudDeployment_10.2306.0.47.zip" ; Description="Cloud Deployment Package"} + $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2210608" ; FileName="Verify-CloudDeployment.zip_Hash.ps1" ; Description="Verify Cloud Deployment PowerShell"} + + foreach ($file in $files){ + if (-not (Test-Path "$downloadfolder\$($file.filename)")){ + Start-BitsTransfer -Source $file.uri -Destination "$downloadfolder\$($file.filename)" -DisplayName "Downloading: $($file.filename)" + } + } + + #Start bootstrap (script is looking for file "CloudDeployment_*.zip" + & $downloadfolder\BootstrapCloudDeploymentTool.ps1 + + #create authentication token (Service Principal or MFA) + if ($SPAppID){ + $SPNsecStringPassword = ConvertTo-SecureString $SPNSecret -AsPlainText -Force + $SPNCred=New-Object System.Management.Automation.PSCredential ($SPAppID, $SPNsecStringPassword) + }else{ + Set-AuthenticationToken -RegistrationCloudName AzureCloud -RegistrationSubscriptionID $SubscriptionID } - $Creds=New-AzADAppCredential -PasswordCredentials $credential -ApplicationID $SP.AppID - $SPNSecret=$Creds.SecretText - Write-Host "Your Password is: " -NoNewLine ; Write-Host $SPNSecret -ForegroundColor Cyan - $SPNsecStringPassword = ConvertTo-SecureString $SPNSecret -AsPlainText -Force - $SPNCred=New-Object System.Management.Automation.PSCredential ($SP.AppID, $SPNsecStringPassword) #create config.json + #add servers to trusted hosts so you can query IP address dynamically (in the lab we dont exactly now which adapter is first and what IP was assigned + $TrustedHosts=@() + $TrustedHosts+=$Servers + Set-Item WSMan:\localhost\Client\TrustedHosts -Value $($TrustedHosts -join ',') -Force + $Content=@" { - "Version": "3.0.0.0", + "Version": "10.0.0.0", "ScaleUnits": [ { "DeploymentData": { "SecuritySettings": { - "SecurityModeSealed": true, - "SecuredCoreEnforced": true, - "VBSProtection": true, "HVCIProtection": true, "DRTMProtection": true, - "KernelDMAProtection": true, "DriftControlEnforced": true, - "CredentialGuardEnforced": false, + "CredentialGuardEnforced": true, "SMBSigningEnforced": true, "SMBClusterEncryption": false, "SideChannelMitigationEnforced": true, "BitlockerBootVolume": true, - "BitlockerDataVolumes": true, - "SEDProtectionEnforced": true, + "BitlockerDataVolumes": false, "WDACEnforced": true }, "Observability": { @@ -172,6 +245,10 @@ }, "Cluster": { "Name": "ASClus01", + "WitnessType": "Cloud", + "WitnessPath": "", + "CloudAccountName": "$StorageAccountName", + "AzureServiceEndpoint": "core.windows.net", "StaticAddress": [ "" ] @@ -179,15 +256,9 @@ "Storage": { "ConfigurationMode": "Express" }, - "OptionalServices": { - "VirtualSwitchName": "", - "CSVPath": "", - "ARBRegion": "westeurope" - }, "TimeZone": "Pacific Standard Time", "NamingPrefix": "ASClus01", "DomainFQDN": "corp.contoso.com", - "ExternalDomainFQDN": "corp.contoso.com", "InfrastructureNetwork": [ { "VlanId": "0", @@ -196,7 +267,7 @@ "IPPools": [ { "StartingAddress": "10.0.0.100", - "EndingAddress": "10.0.0.199" + "EndingAddress": "10.0.0.110" } ], "DNSServers": [ @@ -276,10 +347,14 @@ ] } "@ -$Content | Out-File -FilePath d:\config.json +$Content | Out-File -FilePath c:\config.json + +#set trusted hosts back +Set-Item WSMan:\localhost\Client\TrustedHosts -Value "" -Force #start deployment #make sure some prereqs (that will be fixed in future) are set +<# #Make sure Windows Update is disabled and ping enabled (https://learn.microsoft.com/en-us/azure-stack/hci/hci-known-issues-2303) Microsoft.PowerShell.Core\Invoke-Command -ComputerName $Servers -ScriptBlock { reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU /v NoAutoUpdate /t REG_DWORD /d 1 /f @@ -290,19 +365,25 @@ $Content | Out-File -FilePath d:\config.json } #add hostnames and IPs to trusted hosts (bug that in BareMetal.psm1 is invoke-command with IP that is not in trusted hosts) $TrustedHosts=@() - $TrustedHosts+=(Get-NetIPAddress -CimSession $Servers -InterfaceAlias Ethernet* -AddressFamily IPv4).IPAddress $TrustedHosts+=$Servers Set-Item WSMan:\localhost\Client\TrustedHosts -Value $($TrustedHosts -join ',') -Force +#> -#deploy -.\Invoke-CloudDeployment -JSONFilePath D:\config.json -AzureStackLCMUserCredential $AzureStackLCMUserCredential -LocalAdminCredential $LocalAdminCred -RegistrationSPCredential $SPNCred -RegistrationCloudName $CloudName -RegistrationSubscriptionID $SubscriptionID +#create secured storage access key +$StorageAccountAccessKeySecured = ConvertTo-SecureString $StorageAccountAccessKey -AsPlainText -Force +#deploy +if ($SPAppID){ + .\Invoke-CloudDeployment -JSONFilePath c:\config.json -AzureStackLCMUserCredential $AzureStackLCMUserCredential -LocalAdminCredential $LocalAdminCred -RegistrationSPCredential $SPNCred -RegistrationCloudName AzureCloud -RegistrationSubscriptionID $SubscriptionID -RegistrationResourceGroupName $ResourceGroupName -WitnessStorageKey $StorageAccountAccessKeySecured -RegistrationRegion $Location +}else{ + .\Invoke-CloudDeployment -JSONFilePath c:\config.json -AzureStackLCMUserCredential $AzureStackLCMUserCredential -LocalAdminCredential $LocalAdminCred -RegistrationCloudName AzureCloud -RegistrationSubscriptionID $SubscriptionID -RegistrationResourceGroupName $ResourceGroupName -WitnessStorageKey $StorageAccountAccessKeySecured -RegistrationRegion $Location +} #endregion #region Validate deployment - run from management VM! $SeedNode="ASNode1" Invoke-Command -ComputerName $SeedNode -ScriptBlock { - ([xml](Get-Content C:\ecestore\efb61d70-47ed-8f44-5d63-bed6adc0fb0f\086a22e3-ef1a-7b3a-dc9d-f407953b0f84)) | Select-Xml -XPath "//Action/Steps/Step" | ForEach-Object { $_.Node } | Select-Object FullStepIndex, Status, Name, StartTimeUtc, EndTimeUtc, @{Name="Duration";Expression={new-timespan -Start $_.StartTimeUtc -End $_.EndTimeUtc } } | ft -AutoSize + ([xml](Get-Content C:\ecestore\efb61d70-47ed-8f44-5d63-bed6adc0fb0f\086a22e3-ef1a-7b3a-dc9d-f407953b0f84)) | Select-Xml -XPath "//Action/Steps/Step" | ForEach-Object { $_.Node } | Select-Object FullStepIndex, Status, Name, StartTimeUtc, EndTimeUtc, @{Name="Duration";Expression={new-timespan -Start $_.StartTimeUtc -End $_.EndTimeUtc } } | Format-Table -AutoSize } #endregion \ No newline at end of file From c1c011a663c2419fdf0596f3012084053eaf4645 Mon Sep 17 00:00:00 2001 From: Jaromirm Date: Sat, 29 Jul 2023 23:44:24 +0200 Subject: [PATCH 4/5] Updated disk size (127GB) and added 23H2 --- Scripts/1_Prereq.ps1 | 2 +- Scripts/2_CreateParentDisks.ps1 | 18 +++++++++--------- Tools/CreateParentDisk.ps1 | 11 +++++++---- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Scripts/1_Prereq.ps1 b/Scripts/1_Prereq.ps1 index b4fc34ff..2d12aa26 100644 --- a/Scripts/1_Prereq.ps1 +++ b/Scripts/1_Prereq.ps1 @@ -154,7 +154,7 @@ If ( Test-Path -Path "$PSScriptRoot\Temp\Convert-WindowsImage.ps1" ) { $downloadurl = $webcontent.BaseResponse.ResponseUri.AbsoluteUri.Substring(0,$webcontent.BaseResponse.ResponseUri.AbsoluteUri.LastIndexOf('/'))+($webcontent.Links | where-object { $_.'data-url' -match '/Diskspd.*zip$' }|Select-Object -ExpandProperty "data-url") } #> - $downloadurl="https://github.com/microsoft/diskspd/releases/download/v2.0.21a/DiskSpd.zip" + $downloadurl="https://github.com/microsoft/diskspd/releases/download/v2.1/DiskSpd.ZIP" Invoke-WebRequest -Uri $downloadurl -OutFile "$PSScriptRoot\Temp\ToolsVHD\DiskSpd\diskspd.zip" }catch{ WriteError "`t Failed to download Diskspd!" diff --git a/Scripts/2_CreateParentDisks.ps1 b/Scripts/2_CreateParentDisks.ps1 index a6fe162d..fe4c1d05 100644 --- a/Scripts/2_CreateParentDisks.ps1 +++ b/Scripts/2_CreateParentDisks.ps1 @@ -354,13 +354,13 @@ If (-not $isAdmin) { Kind = "Full" Edition="4" VHDName="Win2016_G2.vhdx" - Size=60GB + Size=127GB } $ServerVHDs += @{ Kind = "Core" Edition="3" VHDName="Win2016Core_G2.vhdx" - Size=30GB + Size=127GB } <# Removed since it does not work with newer than 14393.2724 $ServerVHDs += @{ @@ -376,13 +376,13 @@ If (-not $isAdmin) { Kind = "Full" Edition="4" VHDName="Win2019_G2.vhdx" - Size=60GB + Size=127GB } $ServerVHDs += @{ Kind = "Core" Edition="3" VHDName="Win2019Core_G2.vhdx" - Size=30GB + Size=127GB } }elseif ($BuildNumber -eq 20348){ #Windows Server 2022 @@ -390,20 +390,20 @@ If (-not $isAdmin) { Kind = "Full" Edition="4" VHDName="Win2022_G2.vhdx" - Size=60GB + Size=127GB } $ServerVHDs += @{ Kind = "Core" Edition="3" VHDName="Win2022Core_G2.vhdx" - Size=30GB + Size=127GB } }elseif ($BuildNumber -gt 20348 -and $SAC){ $ServerVHDs += @{ Kind = "Core" Edition="2" VHDName="WinSrvInsiderCore_$BuildNumber.vhdx" - Size=30GB + Size=127GB } #DCEdition fix if ($LabConfig.DCEdition -gt 2){ @@ -415,13 +415,13 @@ If (-not $isAdmin) { Kind = "Full" Edition="4" VHDName="WinSrvInsider_$BuildNumber.vhdx" - Size=60GB + Size=127GB } $ServerVHDs += @{ Kind = "Core" Edition="3" VHDName="WinSrvInsiderCore_$BuildNumber.vhdx" - Size=30GB + Size=127GB } }else{ $ISOServer | Dismount-DiskImage diff --git a/Tools/CreateParentDisk.ps1 b/Tools/CreateParentDisk.ps1 index f4931583..cab66585 100644 --- a/Tools/CreateParentDisk.ps1 +++ b/Tools/CreateParentDisk.ps1 @@ -161,11 +161,14 @@ If (-not $isAdmin) { 20348 { "AzSHCI21H2_G2.vhdx" } - 22621 { + 20349 { "AzSHCI22H2_G2.vhdx" } + 25398 { + "AzSHCI23H2_G2.vhdx" + } } - if ($BuildNumber -GT 20348){ + if ($BuildNumber -GT 25398){ $tempvhdname="AzSHCIInsider_$BuildNumber.vhdx" } }elseif (($Edition -like "*Server*Core*") -or ($Edition -like "Windows Server * Datacenter") -or ($Edition -like "Windows Server * Standard")){ @@ -314,9 +317,9 @@ If (-not $isAdmin) { if(!$vhdname){$vhdname=$tempvhdname} #ask for size - [int64]$size=(Read-Host -Prompt "Please type size of the Image in GB. If nothing specified, 60 is used") + [int64]$size=(Read-Host -Prompt "Please type size of the Image in GB. If nothing specified, 127 is used") $size=$size*1GB - if (!$size){$size=60GB} + if (!$size){$size=127GB} #Create VHD if ($nanoserver -eq "y"){ From e2c432b23ffa26ed17516fcfdf896940d5d3a3d4 Mon Sep 17 00:00:00 2001 From: Jaromirm Date: Sat, 29 Jul 2023 23:47:17 +0200 Subject: [PATCH 5/5] minor fixes --- .../LabConfig.ps1 | 2 +- .../Scenario.ps1 | 10 +- Scenarios/AzSHCI and AVD/Scenario.ps1 | 2 +- Scenarios/AzSHCI and Kubernetes/Scenario.ps1 | 19 +- Scenarios/AzSHCI and MDT/Scenario.ps1 | 2079 ++++++++--------- .../Scenario.ps1 | 57 +- 6 files changed, 1103 insertions(+), 1066 deletions(-) diff --git a/Scenarios/AzSHCI Deployment 22H2 Edition/LabConfig.ps1 b/Scenarios/AzSHCI Deployment 22H2 Edition/LabConfig.ps1 index c3511b92..647a7953 100644 --- a/Scenarios/AzSHCI Deployment 22H2 Edition/LabConfig.ps1 +++ b/Scenarios/AzSHCI Deployment 22H2 Edition/LabConfig.ps1 @@ -1,6 +1,6 @@ $LabConfig=@{AllowedVLANs="1-10,711-719" ; DomainAdminName='LabAdmin'; AdminPassword='LS1setup!' ; DCEdition='4'; Internet=$true ; TelemetryLevel='Full' ; TelemetryNickname='' ; AdditionalNetworksConfig=@(); VMs=@()} -#Azure Stack HCI 21H2 +#Azure Stack HCI 22H2 1..4 | ForEach-Object {$LABConfig.VMs += @{ VMName = "AzSHCI$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI22H2_G2.vhdx' ; HDDNumber = 4 ; HDDSize= 2TB ; MemoryStartupBytes= 1GB; VMProcessorCount=4 ; vTPM=$true}} #Or with nested virtualization enabled #1..4 | ForEach-Object {$LABConfig.VMs += @{ VMName = "AzSHCI$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI22H2_G2.vhdx' ; HDDNumber = 4 ; HDDSize= 2TB ; MemoryStartupBytes= 4GB; VMProcessorCount=4 ; vTPM=$true ; NestedVirt=$true}} diff --git a/Scenarios/AzSHCI Deployment 22H2 Edition/Scenario.ps1 b/Scenarios/AzSHCI Deployment 22H2 Edition/Scenario.ps1 index 28cb7b8e..870f28b0 100644 --- a/Scenarios/AzSHCI Deployment 22H2 Edition/Scenario.ps1 +++ b/Scenarios/AzSHCI Deployment 22H2 Edition/Scenario.ps1 @@ -171,9 +171,9 @@ } } #define and install other features - $features="Failover-Clustering","RSAT-Clustering-PowerShell","Hyper-V-PowerShell","NetworkATC","NetworkHUD","Data-Center-Bridging","RSAT-DataCenterBridging-LLDP-Tools","FS-SMBBW" - #optional - affects perf even if not enabled on volumes as filter driver is attached (Bitlocker,SR,Dedup) - #$features+="RSAT-Feature-Tools-BitLocker","Storage-Replica","RSAT-Storage-Replica","FS-Data-Deduplication","System-Insights","RSAT-System-Insights" + $features="Failover-Clustering","RSAT-Clustering-PowerShell","Hyper-V-PowerShell","NetworkATC","NetworkHUD","Data-Center-Bridging","RSAT-DataCenterBridging-LLDP-Tools","FS-SMBBW","System-Insights","RSAT-System-Insights" + #optional - affects perf even if not enabled on volumes as filter driver is attached (SR,Dedup) and also Bitlocker, that affects a little bit + #$features+="Storage-Replica","RSAT-Storage-Replica","FS-Data-Deduplication","BitLocker","RSAT-Feature-Tools-BitLocker" Invoke-Command -ComputerName $servers -ScriptBlock {Install-WindowsFeature -Name $using:features} #endregion @@ -823,6 +823,10 @@ if ($NetATC){ #check number of live migrations get-vmhost -CimSession $Servers | Select-Object Name,MaximumVirtualMachineMigrations + + #check it in cluster (is only 1 - expected) + get-cluster -Name $ClusterName | Select-Object Name,MaximumParallelMigrations + #endregion #remove net intent global overrides if necessary diff --git a/Scenarios/AzSHCI and AVD/Scenario.ps1 b/Scenarios/AzSHCI and AVD/Scenario.ps1 index 2deab1c9..bc1b8a8b 100644 --- a/Scenarios/AzSHCI and AVD/Scenario.ps1 +++ b/Scenarios/AzSHCI and AVD/Scenario.ps1 @@ -392,7 +392,7 @@ #Copy agent and bootloader to VMs #create sessions $sessions=New-PSSession -ComputerName $VMs.VMName - #copy ARC agent + #copy AVD agent foreach ($session in $sessions){ Copy-Item -Path "$env:USERPROFILE\Downloads\AVDAgent.msi" -Destination "$env:USERPROFILE\Downloads\" -tosession $session -force Copy-Item -Path "$env:USERPROFILE\Downloads\AVDAgentBootloader.msi" -Destination "$env:USERPROFILE\Downloads\" -tosession $session -force diff --git a/Scenarios/AzSHCI and Kubernetes/Scenario.ps1 b/Scenarios/AzSHCI and Kubernetes/Scenario.ps1 index 52d38c87..8befe37c 100644 --- a/Scenarios/AzSHCI and Kubernetes/Scenario.ps1 +++ b/Scenarios/AzSHCI and Kubernetes/Scenario.ps1 @@ -276,16 +276,17 @@ Foreach ($PSSession in $PSSessions){ $VolumeName="AKS" $Servers=(Get-ClusterNode -Cluster $ClusterName).Name $DHCPServer="DC" - $DHCPScopeID="10.0.0.0" $VIPPoolStart="10.0.1.2" $VIPPoolEnd="10.0.1.100" + $VLANID=11 + $resourcegroupname="$ClusterName-rg" + + #if dhcp is disabled: $k8sNodeIpPoolStart="10.0.1.101" $k8sNodeIpPoolEnd="10.0.1.254" $IPAddressPrefix="10.0.1.0/24" $DNSServers="10.0.1.1" $Gateway="10.0.1.1" - $VLANID=11 - $resourcegroupname="$ClusterName-rg" #JaromirK note: it would be great if I could simply run "Initialize-AksHciNode -ComputerName $ClusterName". I could simply skip credssp. Same applies for AksHciConfig and AksHciRegistration @@ -323,11 +324,13 @@ Foreach ($PSSession in $PSSessions){ #configure aks #note: I'm assigning larger control plane VM than default as I saw IP disapperaring IP address if it was smaller in virtual environment (I tested manually incresed size to 8cores and 8GB RAM) Invoke-Command -ComputerName $servers[0] -Credential $Credentials -Authentication Credssp -ScriptBlock { + #install nuget first + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force #DHCP - #$vnet = New-AksHciNetworkSetting -Name $using:vNetName -vSwitchName $using:vSwitchName -vippoolstart $using:vippoolstart -vippoolend $using:vippoolend + $vnet = New-AksHciNetworkSetting -Name $using:vNetName -vSwitchName $using:vSwitchName -vippoolstart $using:vippoolstart -vippoolend $using:vippoolend -vlanID $using:VLANID #Static - $vnet = New-AksHciNetworkSetting -Name $using:vNetName -ipAddressPrefix $using:IPAddressPrefix -vSwitchName $using:vSwitchName -vippoolstart $using:vippoolstart -vippoolend $using:vippoolend -k8sNodeIpPoolStart $using:k8sNodeIpPoolStart -k8sNodeIpPoolEnd $using:k8sNodeIpPoolEnd -vlanID $using:VLANID -DNSServers $using:DNSServers -gateway $Using:Gateway - Set-AksHciConfig -vnet $vnet -workingDir c:\clusterstorage\$using:VolumeName\ImagesStore -imageDir c:\clusterstorage\$using:VolumeName\Images -cloudConfigLocation c:\clusterstorage\$using:VolumeName\Config -ClusterRoleName "$($using:ClusterName)_AKS" -controlPlaneVmSize 'Standard_A4_v2' # Get-AksHciVmSize + #$vnet = New-AksHciNetworkSetting -Name $using:vNetName -ipAddressPrefix $using:IPAddressPrefix -vSwitchName $using:vSwitchName -vippoolstart $using:vippoolstart -vippoolend $using:vippoolend -k8sNodeIpPoolStart $using:k8sNodeIpPoolStart -k8sNodeIpPoolEnd $using:k8sNodeIpPoolEnd -vlanID $using:VLANID -DNSServers $using:DNSServers -gateway $Using:Gateway + Set-AksHciConfig -vnet $vnet -workingDir c:\clusterstorage\$using:VolumeName\WorkDir -imageDir c:\clusterstorage\$using:VolumeName\Images -cloudConfigLocation c:\clusterstorage\$using:VolumeName\Config -ClusterRoleName "$($using:ClusterName)_AKS" -controlPlaneVmSize 'Standard_A4_v2' # Get-AksHciVmSize } #validate config @@ -518,7 +521,7 @@ $password="" #if blank, password will be created #create credentials $ClientID=$sp.AppId $SecureSecret= ConvertTo-SecureString $password -AsPlainText -Force -$Credentials = New-Object System.Management.Automation.PSCredential ($ClientID , $SecureSecret) +$SPCredentials = New-Object System.Management.Automation.PSCredential ($ClientID , $SecureSecret) #register namespace Microsoft.KubernetesConfiguration and Microsoft.Kubernetes Register-AzResourceProvider -ProviderNamespace Microsoft.Kubernetes @@ -529,7 +532,7 @@ Invoke-Command -ComputerName $ClusterName -ScriptBlock { #Generate kubeconfig Get-AksHciCredential -Name $using:KubernetesClusterName -confirm:0 #onboard - Enable-AksHciArcConnection -Name $using:KubernetesClusterName -tenantId $using:tenantID -subscriptionId $using:subscriptionID -resourcegroup $using:resourcegroup -Location $using:location -credential $using:Credentials + Enable-AksHciArcConnection -Name $using:KubernetesClusterName -tenantId $using:tenantID -subscriptionId $using:subscriptionID -resourcegroup $using:resourcegroup -Location $using:location -credential $using:SPCredentials } diff --git a/Scenarios/AzSHCI and MDT/Scenario.ps1 b/Scenarios/AzSHCI and MDT/Scenario.ps1 index eaab84d9..c43aef70 100644 --- a/Scenarios/AzSHCI and MDT/Scenario.ps1 +++ b/Scenarios/AzSHCI and MDT/Scenario.ps1 @@ -1,244 +1,244 @@ #run all from DC or management machine (Windows 11 or Windows Server 2022) #region variables - $MDTServer="MDT" - $DeploymentShareLocation="D:\DeploymentShare" - $Connection="TCPIP" #or "NamedPipes" - $downloadfolder="$env:USERPROFILE\Downloads" - $WDSRoot="D:\RemoteInstall" - $DHCPServer="DC" - $ScopeID="10.0.0.0" - - $CredSSPUserName="CORP\LabAdmin" - $CredSSPPassword="LS1setup!" +$MDTServer="MDT" +$DeploymentShareLocation="D:\DeploymentShare" +$Connection="TCPIP" #or "NamedPipes" +$downloadfolder="$env:USERPROFILE\Downloads" +$WDSRoot="D:\RemoteInstall" +$DHCPServer="DC" +$ScopeID="10.0.0.0" + +$CredSSPUserName="CORP\LabAdmin" +$CredSSPPassword="LS1setup!" #endregion #region prereqs - #install management features (ADDS, DHCP,...) - $WindowsInstallationType=Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\' -Name InstallationType - If ($WindowsInstallationType -like "Server*"){ - Install-WindowsFeature -Name "RSAT-AD-PowerShell","RSAT-ADDS","RSAT-DHCP","RSAT-DNS-Server","WDS-AdminPack" - }else{ - $Capabilities="Rsat.ServerManager.Tools~~~~0.0.1.0","Rsat.DHCP.Tools~~~~0.0.1.0","Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0","Rsat.Dns.Tools~~~~0.0.1.0" - foreach ($Capability in $Capabilities){ - Add-WindowsCapability -Name $Capability -Online - } +#install management features (ADDS, DHCP,...) +$WindowsInstallationType=Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\' -Name InstallationType +If ($WindowsInstallationType -like "Server*"){ + Install-WindowsFeature -Name "RSAT-AD-PowerShell","RSAT-ADDS","RSAT-DHCP","RSAT-DNS-Server","WDS-AdminPack" +}else{ + $Capabilities="Rsat.ServerManager.Tools~~~~0.0.1.0","Rsat.DHCP.Tools~~~~0.0.1.0","Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0","Rsat.Dns.Tools~~~~0.0.1.0" + foreach ($Capability in $Capabilities){ + Add-WindowsCapability -Name $Capability -Online } +} - #download and install binaries - #Download files - $files=@() - #$Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2026036" ; FileName="adksetup.exe" ; Description="Windows 10 ADK 1809"} - #$Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2022233" ; FileName="adkwinpesetup.exe" ; Description="WindowsPE 1809"} - $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2165884" ; FileName="adksetup.exe" ; Description="Windows 11 21H2 ADK"} - $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2166133" ; FileName="adkwinpesetup.exe" ; Description="WindowsPE for Windows 11 21H2"} - $Files+=@{Uri="https://download.microsoft.com/download/3/3/9/339BE62D-B4B8-4956-B58D-73C4685FC492/MicrosoftDeploymentToolkit_x64.msi" ; FileName="MicrosoftDeploymentToolkit_x64.msi" ; Description="Microsoft Deployment Toolkit"} - #$Files+=@{Uri="https://software-download.microsoft.com/download/pr/AzureStackHCI_17784.1408_EN-US.iso" ; FileName="AzureStackHCI_17784.1408_EN-US.iso" ; Description="Azure Stack HCI ISO"} - #$Files+=@{Uri="https://software-static.download.prss.microsoft.com/sg/download/888969d5-f34g-4e03-ac9d-1f9786c66749/AzureStackHCI_20348.587_en-us.iso" ; FileName="AzureStackHCI_20348.587_en-us.iso" ; Description="Azure Stack HCI ISO"} - $Files+=@{Uri="https://software-static.download.prss.microsoft.com/dbazure/988969d5-f34g-4e03-ac9d-1f9786c66751/20349.1129.221007-2120.fe_release_hciv3_svc_refresh_SERVERAZURESTACKHCICOR_OEMRET_x64FRE_en-us.iso" ; FileName="AzureStackHCI_20349.1129_en-us.iso" ; Description="Azure Stack HCI ISO"} - $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=866658" ; FileName="SQL2019-SSEI-Expr.exe" ; Description="SQL Express 2019"} - #$Files+=@{Uri="https://aka.ms/ssmsfullsetup" ; FileName="SSMS-Setup-ENU.exe" ; Description="SQL Management Studio"} - foreach ($file in $files){ - if (-not (Test-Path "$downloadfolder\$($file.filename)")){ - Start-BitsTransfer -Source $file.uri -Destination "$downloadfolder\$($file.filename)" -DisplayName "Downloading: $($file.filename)" - } +#download and install binaries + #Download files + $files=@() + #$Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2026036" ; FileName="adksetup.exe" ; Description="Windows 10 ADK 1809"} + #$Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2022233" ; FileName="adkwinpesetup.exe" ; Description="WindowsPE 1809"} + $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2196127" ; FileName="adksetup.exe" ; Description="Windows 11 22H2 ADK"} + $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=2196224" ; FileName="adkwinpesetup.exe" ; Description="WindowsPE for Windows 11 22H2"} + $Files+=@{Uri="https://download.microsoft.com/download/3/3/9/339BE62D-B4B8-4956-B58D-73C4685FC492/MicrosoftDeploymentToolkit_x64.msi" ; FileName="MicrosoftDeploymentToolkit_x64.msi" ; Description="Microsoft Deployment Toolkit"} + #$Files+=@{Uri="https://software-download.microsoft.com/download/pr/AzureStackHCI_17784.1408_EN-US.iso" ; FileName="AzureStackHCI_17784.1408_EN-US.iso" ; Description="Azure Stack HCI ISO"} + #$Files+=@{Uri="https://software-static.download.prss.microsoft.com/sg/download/888969d5-f34g-4e03-ac9d-1f9786c66749/AzureStackHCI_20348.587_en-us.iso" ; FileName="AzureStackHCI_20348.587_en-us.iso" ; Description="Azure Stack HCI ISO"} + $Files+=@{Uri="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66750/AzureStackHCI_20349.1607_en-us.iso" ; FileName="AzureStackHCI_20349.1607_en-us.iso" ; Description="Azure Stack HCI ISO"} + $Files+=@{Uri="https://go.microsoft.com/fwlink/?linkid=866658" ; FileName="SQL2019-SSEI-Expr.exe" ; Description="SQL Express 2019"} + #$Files+=@{Uri="https://aka.ms/ssmsfullsetup" ; FileName="SSMS-Setup-ENU.exe" ; Description="SQL Management Studio"} + foreach ($file in $files){ + if (-not (Test-Path "$downloadfolder\$($file.filename)")){ + Start-BitsTransfer -Source $file.uri -Destination "$downloadfolder\$($file.filename)" -DisplayName "Downloading: $($file.filename)" } + } - #install ADK - Start-Process -Wait -FilePath "$downloadfolder\adksetup.exe" -ArgumentList "/features OptionId.DeploymentTools OptionId.UserStateMigrationTool /quiet" - #install ADK WinPE - Start-Process -Wait -FilePath "$downloadfolder\adkwinpesetup.exe" -ArgumentList "/features OptionID.WindowsPreinstallationEnvironment /Quiet" - #install MDT locally - Start-Process -Wait -Filepath msiexec.exe -Argumentlist "/i $downloadfolder\MicrosoftDeploymentToolkit_x64.msi /q" - - #prepare MDT Server - #format and prepare "D" drive on MDT Server - Get-Disk -CimSession $MDTServer | Where-Object PartitionStyle -eq RAW | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -UseMaximumSize -AssignDriveLetter | Format-Volume -Filesystem NTFS -AllocationUnitSize 8kb -NewFileSystemLabel "Storage" - #enable SMB FIrewall rule - Enable-NetFirewallRUle -CimSession $MDTServer -Name FPS-SMB-In-TCP - #copy binaries - New-Item -Path "\\$MDTServer\d$\" -Name INstall -ItemType Directory - Copy-Item -Path "$env:UserProfile\Downloads\*" -Destination "\\$MDTServer\d$\Install" -Recurse - - #Install MDT and Prereqs to MDT Server - Invoke-Command -ComputerName $MDTServer -ScriptBlock { - $downloadfolder="d:\Install" - #install ADK - Start-Process -Wait -FilePath "$downloadfolder\adksetup.exe" -ArgumentList "/features OptionId.DeploymentTools OptionId.UserStateMigrationTool /quiet" - #install ADK WinPE - Start-Process -Wait -FilePath "$downloadfolder\adkwinpesetup.exe" -ArgumentList "/features OptionID.WindowsPreinstallationEnvironment /Quiet" - #install MDT - Start-Process -Wait -Filepath msiexec.exe -Argumentlist "/i $downloadfolder\MicrosoftDeploymentToolkit_x64.msi /q" - } + #install ADK + Start-Process -Wait -FilePath "$downloadfolder\adksetup.exe" -ArgumentList "/features OptionId.DeploymentTools OptionId.UserStateMigrationTool /quiet" + #install ADK WinPE + Start-Process -Wait -FilePath "$downloadfolder\adkwinpesetup.exe" -ArgumentList "/features OptionID.WindowsPreinstallationEnvironment /Quiet" + #install MDT locally + Start-Process -Wait -Filepath msiexec.exe -Argumentlist "/i $downloadfolder\MicrosoftDeploymentToolkit_x64.msi /q" + + #prepare MDT Server + #format and prepare "D" drive on MDT Server + Get-Disk -CimSession $MDTServer | Where-Object PartitionStyle -eq RAW | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -UseMaximumSize -AssignDriveLetter | Format-Volume -Filesystem NTFS -AllocationUnitSize 8kb -NewFileSystemLabel "Storage" + #enable SMB FIrewall rule + Enable-NetFirewallRUle -CimSession $MDTServer -Name FPS-SMB-In-TCP + #copy binaries + New-Item -Path "\\$MDTServer\d$\" -Name INstall -ItemType Directory + Copy-Item -Path "$env:UserProfile\Downloads\*" -Destination "\\$MDTServer\d$\Install" -Recurse + + #Install MDT and Prereqs to MDT Server + Invoke-Command -ComputerName $MDTServer -ScriptBlock { + $downloadfolder="d:\Install" + #install ADK + Start-Process -Wait -FilePath "$downloadfolder\adksetup.exe" -ArgumentList "/features OptionId.DeploymentTools OptionId.UserStateMigrationTool /quiet" + #install ADK WinPE + Start-Process -Wait -FilePath "$downloadfolder\adkwinpesetup.exe" -ArgumentList "/features OptionID.WindowsPreinstallationEnvironment /Quiet" + #install MDT + Start-Process -Wait -Filepath msiexec.exe -Argumentlist "/i $downloadfolder\MicrosoftDeploymentToolkit_x64.msi /q" + } - #install SQL Express to MDT Machine (using credssp) - # Temporarily enable CredSSP delegation to avoid double-hop issue - $WindowsInstallationType=Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\' -Name InstallationType - If ($WindowsInstallationType -eq "Client"){ - winrm quickconfig -force #on client is winrm not configured - } - Enable-WSManCredSSP -Role "Client" -DelegateComputer $MDTServer -Force - Invoke-Command -ComputerName $MDTServer -ScriptBlock { Enable-WSManCredSSP Server -Force } - - $SecureStringPassword = ConvertTo-SecureString $CredSSPPassword -AsPlainText -Force - $Credentials = New-Object System.Management.Automation.PSCredential ($CredSSPUserName, $SecureStringPassword) - - Invoke-Command -ComputerName $MDTServer -Credential $Credentials -Authentication Credssp -ScriptBlock { - $downloadfolder="D:\Install" - #install - $exec="$downloadfolder\SQL2019-SSEI-Expr.exe" - $params="/Action=Install /MediaPath=$downloadfolder\SQLMedia /IAcceptSqlServerLicenseTerms /quiet" - Start-Process -FilePath $exec -ArgumentList $params -NoNewWindow -Wait - } + #install SQL Express to MDT Machine (using credssp) + # Temporarily enable CredSSP delegation to avoid double-hop issue + $WindowsInstallationType=Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\' -Name InstallationType + If ($WindowsInstallationType -eq "Client"){ + winrm quickconfig -force #on client is winrm not configured + } + Enable-WSManCredSSP -Role "Client" -DelegateComputer $MDTServer -Force + Invoke-Command -ComputerName $MDTServer -ScriptBlock { Enable-WSManCredSSP Server -Force } + + $SecureStringPassword = ConvertTo-SecureString $CredSSPPassword -AsPlainText -Force + $Credentials = New-Object System.Management.Automation.PSCredential ($CredSSPUserName, $SecureStringPassword) + + Invoke-Command -ComputerName $MDTServer -Credential $Credentials -Authentication Credssp -ScriptBlock { + $downloadfolder="D:\Install" + #install + $exec="$downloadfolder\SQL2019-SSEI-Expr.exe" + $params="/Action=Install /MediaPath=$downloadfolder\SQLMedia /IAcceptSqlServerLicenseTerms /quiet" + Start-Process -FilePath $exec -ArgumentList $params -NoNewWindow -Wait + } - # Disable CredSSP - Disable-WSManCredSSP -Role Client - Invoke-Command -ComputerName $MDTServer -ScriptBlock {Disable-WSManCredSSP Server} + # Disable CredSSP + Disable-WSManCredSSP -Role Client + Invoke-Command -ComputerName $MDTServer -ScriptBlock {Disable-WSManCredSSP Server} #endregion #region configure MDT - #import MDT Module - Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" - #list commands - Get-Command -Module MicrosoftDeploymentToolkit - - #Create new Deployment Share - #Create Deployment Share - Invoke-Command -ComputerName $MDTServer -ScriptBlock { - New-Item -Path $using:DeploymentShareLocation -ItemType Directory -ErrorAction Ignore - New-SmbShare -Name "DeploymentShare$" -Path "$using:DeploymentShareLocation" -FullAccess Administrators +#import MDT Module +Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" +#list commands +Get-Command -Module MicrosoftDeploymentToolkit + +#Create new Deployment Share + #Create Deployment Share +Invoke-Command -ComputerName $MDTServer -ScriptBlock { + New-Item -Path $using:DeploymentShareLocation -ItemType Directory -ErrorAction Ignore + New-SmbShare -Name "DeploymentShare$" -Path "$using:DeploymentShareLocation" -FullAccess Administrators +} +#map MDT deployment share as PSDrive +#sometimes happens that script to complains: The process cannot access the file '\\MDT\DeploymentShare$\Control\Settings.xml' because it is being used by another process. +do{ + New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose + if (-not (get-psdrive -Name DS001)){ + Write-Output "Failed adding PSDrive - trying again" } - #map MDT deployment share as PSDrive - #sometimes happens that script to complains: The process cannot access the file '\\MDT\DeploymentShare$\Control\Settings.xml' because it is being used by another process. - do{ - New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose - if (-not (get-psdrive -Name DS001)){ - Write-Output "Failed adding PSDrive - trying again" - } - }until (get-psdrive -Name DS001) - #Configure SQL Services - - Invoke-Command -ComputerName $MDTServer -scriptblock { - if ($using:Connection -eq "NamedPipes"){ - #Named Pipes - Set-ItemProperty -Path "hklm:\\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQLServer\SuperSocketNetLib\Np\" -Name Enabled -Value 1 - } +}until (get-psdrive -Name DS001) +#Configure SQL Services - if ($using:Connection -eq "TCPIP"){ - #TCP - Set-ItemProperty -Path "hklm:\\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQLServer\SuperSocketNetLib\Tcp\" -Name Enabled -Value 1 - Set-ItemProperty -Path "hklm:\\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQLServer\SuperSocketNetLib\Tcp\IPAll" -Name TcpPort -Value 1433 - } +Invoke-Command -ComputerName $MDTServer -scriptblock { + if ($using:Connection -eq "NamedPipes"){ + #Named Pipes + Set-ItemProperty -Path "hklm:\\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQLServer\SuperSocketNetLib\Np\" -Name Enabled -Value 1 + } - Restart-Service 'MSSQL$SQLEXPRESS' - Set-Service -Name SQLBrowser -StartupType Automatic - Start-Service -Name SQLBrowser + if ($using:Connection -eq "TCPIP"){ + #TCP + Set-ItemProperty -Path "hklm:\\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQLServer\SuperSocketNetLib\Tcp\" -Name Enabled -Value 1 + Set-ItemProperty -Path "hklm:\\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQLServer\SuperSocketNetLib\Tcp\IPAll" -Name TcpPort -Value 1433 } - #create Firewall rule for SQL Server - - if ($Connection -eq "TCPIP"){ - New-NetFirewallRule ` - -CimSession $MDTServer ` - -Action Allow ` - -Name "SQLExpress-In-TCP" ` - -DisplayName "SQLExpress (SQL-In)" ` - -Description "Inbound rule for SQL. [TCP-1433]" ` - -Enabled True ` - -Direction Inbound ` - -Program "%ProgramFiles%\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQL\Binn\sqlservr.exe" ` - -Protocol TCP ` - -LocalPort 1433 ` - -Profile Any ` - -Group "SQL Express" ` - -RemoteAddress Any - } + Restart-Service 'MSSQL$SQLEXPRESS' + Set-Service -Name SQLBrowser -StartupType Automatic + Start-Service -Name SQLBrowser +} + +#create Firewall rule for SQL Server + if ($Connection -eq "TCPIP"){ New-NetFirewallRule ` -CimSession $MDTServer ` -Action Allow ` - -Name "SQLBrowser-In-UDP" ` - -DisplayName "SQLBrowser (SQL-In-UDP)" ` - -Description "Inbound rule for SQLBrowser. [UDP-1434]" ` + -Name "SQLExpress-In-TCP" ` + -DisplayName "SQLExpress (SQL-In)" ` + -Description "Inbound rule for SQL. [TCP-1433]" ` -Enabled True ` -Direction Inbound ` - -Program "%ProgramFiles% (x86)\Microsoft SQL Server\90\Shared\sqlbrowser.exe" ` - -Protocol UDP ` - -LocalPort 1434 ` + -Program "%ProgramFiles%\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQL\Binn\sqlservr.exe" ` + -Protocol TCP ` + -LocalPort 1433 ` -Profile Any ` -Group "SQL Express" ` -RemoteAddress Any - - #create DB in MDT - if ($Connection -eq "NamedPipes"){ - New-MDTDatabase -path "DS001:" -SQLServer $MDTServer -Instance "SQLExpress" -Netlib "DBNMPNTW" -Database "MDTDB" -SQLShare "DeploymentShare$" -Verbose - }elseif ($Connection -eq "TCPIP"){ - New-MDTDatabase -path "DS001:" -SQLServer $MDTServer -Port "1433" -Netlib "DBMSSOCN" -Database "MDTDB" -Verbose } - #Import Operating System - $ISO = Mount-DiskImage -ImagePath "$downloadfolder\AzureStackHCI_20349.1129_en-us.iso" -PassThru - $ISOMediaPath = (Get-Volume -DiskImage $ISO).DriveLetter+':\' - Import-mdtoperatingsystem -path "DS001:\Operating Systems" -SourcePath $ISOMediaPath -DestinationFolder "Azure Stack HCI SERVERAZURESTACKHCICORE x64" -Verbose - - $ISO | Dismount-DiskImage - - #configure Deployment Share properties - Set-ItemProperty DS001:\ -name SupportX86 -value False - Set-ItemProperty DS001:\ -name Boot.x86.GenerateLiteTouchISO -value False - Set-ItemProperty DS001:\ -name Boot.x64.SelectionProfile -value "Nothing" - Set-ItemProperty DS001:\ -name Boot.x64.IncludeNetworkDrivers -value True - Set-ItemProperty DS001:\ -name Boot.x64.IncludeMassStorageDrivers -value True - Set-ItemProperty DS001:\ -name Boot.x64.IncludeAllDrivers -value False - Set-ItemProperty DS001:\ -name Boot.x64.GenerateGenericWIM -value True - #add PowerShell - $Properties=@() - $Properties+=(Get-ItemPropertyValue DS001:\ -Name Boot.x64.FeaturePacks) -split (",") - $FeaturesToAdd="winpe-netfx","winpe-powershell" - foreach ($FeatureToAdd in $FeaturesToAdd){ - if ($properties -notcontains $FeatureToAdd){ - $Properties+=$FeatureToAdd - } + New-NetFirewallRule ` + -CimSession $MDTServer ` + -Action Allow ` + -Name "SQLBrowser-In-UDP" ` + -DisplayName "SQLBrowser (SQL-In-UDP)" ` + -Description "Inbound rule for SQLBrowser. [UDP-1434]" ` + -Enabled True ` + -Direction Inbound ` + -Program "%ProgramFiles% (x86)\Microsoft SQL Server\90\Shared\sqlbrowser.exe" ` + -Protocol UDP ` + -LocalPort 1434 ` + -Profile Any ` + -Group "SQL Express" ` + -RemoteAddress Any + +#create DB in MDT +if ($Connection -eq "NamedPipes"){ + New-MDTDatabase -path "DS001:" -SQLServer $MDTServer -Instance "SQLExpress" -Netlib "DBNMPNTW" -Database "MDTDB" -SQLShare "DeploymentShare$" -Verbose +}elseif ($Connection -eq "TCPIP"){ + New-MDTDatabase -path "DS001:" -SQLServer $MDTServer -Port "1433" -Netlib "DBMSSOCN" -Database "MDTDB" -Verbose +} + +#Import Operating System +$ISO = Mount-DiskImage -ImagePath "$downloadfolder\AzureStackHCI_20349.1607_en-us.iso" -PassThru +$ISOMediaPath = (Get-Volume -DiskImage $ISO).DriveLetter+':\' +Import-mdtoperatingsystem -path "DS001:\Operating Systems" -SourcePath $ISOMediaPath -DestinationFolder "Azure Stack HCI SERVERAZURESTACKHCICORE x64" -Verbose + +$ISO | Dismount-DiskImage + +#configure Deployment Share properties +Set-ItemProperty DS001:\ -name SupportX86 -value False +Set-ItemProperty DS001:\ -name Boot.x86.GenerateLiteTouchISO -value False +Set-ItemProperty DS001:\ -name Boot.x64.SelectionProfile -value "Nothing" +Set-ItemProperty DS001:\ -name Boot.x64.IncludeNetworkDrivers -value True +Set-ItemProperty DS001:\ -name Boot.x64.IncludeMassStorageDrivers -value True +Set-ItemProperty DS001:\ -name Boot.x64.IncludeAllDrivers -value False +Set-ItemProperty DS001:\ -name Boot.x64.GenerateGenericWIM -value True +#add PowerShell +$Properties=@() +$Properties+=(Get-ItemPropertyValue DS001:\ -Name Boot.x64.FeaturePacks) -split (",") +$FeaturesToAdd="winpe-netfx","winpe-powershell" +foreach ($FeatureToAdd in $FeaturesToAdd){ + if ($properties -notcontains $FeatureToAdd){ + $Properties+=$FeatureToAdd } - Set-ItemProperty DS001:\ -name Boot.x64.FeaturePacks -value ($Properties -Join (",")) +} +Set-ItemProperty DS001:\ -name Boot.x64.FeaturePacks -value ($Properties -Join (",")) - #add Task Sequence - import-mdttasksequence -path "DS001:\Task Sequences" -Name "Azure Stack HCI Deploy" -Template "Server.xml" -Comments "" -ID "AzSHCI" -Version "1.0" -OperatingSystemPath "DS001:\Operating Systems\Azure Stack HCI SERVERAZURESTACKHCICORE in Azure Stack HCI SERVERAZURESTACKHCICORE x64 install.wim" -FullName "PFE" -OrgName "Contoso" -HomePage "about:blank" -AdminPassword "LS1setup!" -Verbose +#add Task Sequence +import-mdttasksequence -path "DS001:\Task Sequences" -Name "Azure Stack HCI Deploy" -Template "Server.xml" -Comments "" -ID "AzSHCI" -Version "1.0" -OperatingSystemPath "DS001:\Operating Systems\Azure Stack HCI SERVERAZURESTACKHCICORE in Azure Stack HCI SERVERAZURESTACKHCICORE x64 install.wim" -FullName "PFE" -OrgName "Contoso" -HomePage "about:blank" -AdminPassword "LS1setup!" -Verbose #endregion #region configure MDT run-as account - #create identity for MDT - $DefaultOUPath=(Get-ADDomain).UsersContainer - New-ADUser -Name MDTUser -AccountPassword (ConvertTo-SecureString "LS1setup!" -AsPlainText -Force) -Enabled $True -Path $DefaultOUPath -PasswordNeverExpires $True +#create identity for MDT +$DefaultOUPath=(Get-ADDomain).UsersContainer +New-ADUser -Name MDTUser -AccountPassword (ConvertTo-SecureString "LS1setup!" -AsPlainText -Force) -Enabled $True -Path $DefaultOUPath -PasswordNeverExpires $True - #add FileShare permissions for MDT Account - Invoke-Command -ComputerName $MDTServer -ScriptBlock { - Grant-SmbShareAccess -Name DeploymentShare$ -AccessRight Read -AccountName MDTUser -Confirm:$false - } - #delegate djoin permissions https://www.sevecek.com/EnglishPages/Lists/Posts/Post.aspx?ID=48 - $user = "$env:userdomain\MDTUser" - $ou = (Get-ADDomain).ComputersContainer - - DSACLS $ou /R $user - - DSACLS $ou /I:S /G "$($user):GR;;computer" - DSACLS $ou /I:S /G "$($user):CA;Reset Password;computer" - DSACLS $ou /I:S /G "$($user):WP;pwdLastSet;computer" - DSACLS $ou /I:S /G "$($user):WP;Logon Information;computer" - DSACLS $ou /I:S /G "$($user):WP;description;computer" - DSACLS $ou /I:S /G "$($user):WP;displayName;computer" - DSACLS $ou /I:S /G "$($user):WP;sAMAccountName;computer" - DSACLS $ou /I:S /G "$($user):WP;DNS Host Name Attributes;computer" - DSACLS $ou /I:S /G "$($user):WP;Account Restrictions;computer" - DSACLS $ou /I:S /G "$($user):WP;servicePrincipalName;computer" - DSACLS $ou /I:S /G "$($user):CC;computer;organizationalUnit" +#add FileShare permissions for MDT Account +Invoke-Command -ComputerName $MDTServer -ScriptBlock { + Grant-SmbShareAccess -Name DeploymentShare$ -AccessRight Read -AccountName MDTUser -Confirm:$false +} +#delegate djoin permissions https://www.sevecek.com/EnglishPages/Lists/Posts/Post.aspx?ID=48 +$user = "$env:userdomain\MDTUser" +$ou = (Get-ADDomain).ComputersContainer + +DSACLS $ou /R $user + +DSACLS $ou /I:S /G "$($user):GR;;computer" +DSACLS $ou /I:S /G "$($user):CA;Reset Password;computer" +DSACLS $ou /I:S /G "$($user):WP;pwdLastSet;computer" +DSACLS $ou /I:S /G "$($user):WP;Logon Information;computer" +DSACLS $ou /I:S /G "$($user):WP;description;computer" +DSACLS $ou /I:S /G "$($user):WP;displayName;computer" +DSACLS $ou /I:S /G "$($user):WP;sAMAccountName;computer" +DSACLS $ou /I:S /G "$($user):WP;DNS Host Name Attributes;computer" +DSACLS $ou /I:S /G "$($user):WP;Account Restrictions;computer" +DSACLS $ou /I:S /G "$($user):WP;servicePrincipalName;computer" +DSACLS $ou /I:S /G "$($user):CC;computer;organizationalUnit" #endregion #region configure Bootstrap ini and generate WinPE - #populate bootstrap.ini - $content=@" +#populate bootstrap.ini +$content=@" [Settings] Priority=Default @@ -249,109 +249,109 @@ UserID=MDTUser UserPassword=LS1setup! SkipBDDWelcome=YES "@ - #remove bootstrap.ini first (sometimes there is an error if just populating content) - Invoke-Command -ComputerName $MDTServer -ScriptBlock {Remove-Item -Path "$using:DeploymentShareLocation\Control\Bootstrap.ini" -Force} - #populate content - Set-Content -Path "\\$MDTServer\DeploymentShare$\Control\Bootstrap.ini" -Value $content +#remove bootstrap.ini first (sometimes there is an error if just populating content) +Invoke-Command -ComputerName $MDTServer -ScriptBlock {Remove-Item -Path "$using:DeploymentShareLocation\Control\Bootstrap.ini" -Force} +#populate content +Set-Content -Path "\\$MDTServer\DeploymentShare$\Control\Bootstrap.ini" -Value $content - #update deployment share to generate new WIM for WDS - if (-not(get-module MicrosoftDeploymentToolkit)){ - Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" - } - if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ - New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose - } - update-mdtdeploymentshare -path "DS001:" -verbose -force +#update deployment share to generate new WIM for WDS +if (-not(get-module MicrosoftDeploymentToolkit)){ + Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" +} +if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ + New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose +} +update-mdtdeploymentshare -path "DS001:" -verbose -force #endregion #region Install and configure WDS - #install WDS - Install-WindowsFeature -Name WDS -ComputerName $MDTServer -IncludeManagementTools -IncludeAllSubFeature - - # Temporarily enable CredSSP delegation to avoid double-hop issue - winrm quickconfig -force #on client is winrm not configured - Enable-WSManCredSSP -Role "Client" -DelegateComputer $MDTServer -Force - Invoke-Command -ComputerName $MDTServer -ScriptBlock { Enable-WSManCredSSP Server -Force } - - $SecureStringPassword = ConvertTo-SecureString $CredSSPPassword -AsPlainText -Force - $Credentials = New-Object System.Management.Automation.PSCredential ($CredSSPUserName, $SecureStringPassword) - - #Configure WDS - Invoke-Command -ComputerName $MDTServer -Credential $Credentials -Authentication Credssp -ScriptBlock { - $MDTServer=$using:MDTServer - wdsutil /initialize-server /reminst:$using:WDSRoot - wdsutil /start-server - wdsutil.exe /Set-Server /AnswerClients:Known - #WDSUTIL /Set-Server /AnswerClients:Known /ResponseDelay:4 - WDSUTIL /Set-Server /PxePromptPolicy /known:Noprompt /new:abort - #WDSUTIL /Set-Server /PxePromptPolicy /known:Noprompt /new:Noprompt - } +#install WDS +Install-WindowsFeature -Name WDS -ComputerName $MDTServer -IncludeManagementTools -IncludeAllSubFeature + +# Temporarily enable CredSSP delegation to avoid double-hop issue +winrm quickconfig -force #on client is winrm not configured +Enable-WSManCredSSP -Role "Client" -DelegateComputer $MDTServer -Force +Invoke-Command -ComputerName $MDTServer -ScriptBlock { Enable-WSManCredSSP Server -Force } + +$SecureStringPassword = ConvertTo-SecureString $CredSSPPassword -AsPlainText -Force +$Credentials = New-Object System.Management.Automation.PSCredential ($CredSSPUserName, $SecureStringPassword) + +#Configure WDS +Invoke-Command -ComputerName $MDTServer -Credential $Credentials -Authentication Credssp -ScriptBlock { + $MDTServer=$using:MDTServer + wdsutil /initialize-server /reminst:$using:WDSRoot + wdsutil /start-server + wdsutil.exe /Set-Server /AnswerClients:Known + #WDSUTIL /Set-Server /AnswerClients:Known /ResponseDelay:4 + WDSUTIL /Set-Server /PxePromptPolicy /known:Noprompt /new:abort + #WDSUTIL /Set-Server /PxePromptPolicy /known:Noprompt /new:Noprompt +} - #import the boot media to WDS - Invoke-Command -ComputerName $MDTServer -Credential $Credentials -Authentication Credssp -ScriptBlock { - Get-WdsBootImage | Remove-WdsBootImage - Import-wdsbootimage -path "$($using:DeploymentShareLocation)\Boot\LiteTouchPE_x64.wim" -Verbose - } +#import the boot media to WDS +Invoke-Command -ComputerName $MDTServer -Credential $Credentials -Authentication Credssp -ScriptBlock { + Get-WdsBootImage | Remove-WdsBootImage + Import-wdsbootimage -path "$($using:DeploymentShareLocation)\Boot\LiteTouchPE_x64.wim" -Verbose +} - #Disable CredSSP - Disable-WSManCredSSP -Role Client - Invoke-Command -ComputerName $MDTServer -ScriptBlock {Disable-WSManCredSSP Server} +#Disable CredSSP +Disable-WSManCredSSP -Role Client +Invoke-Command -ComputerName $MDTServer -ScriptBlock {Disable-WSManCredSSP Server} - - #Mitigate issue with Variable Window Extension - Invoke-Command -ComputerName $MDTServer -ScriptBlock { - Wdsutil /Set-TransportServer /EnableTftpVariableWindowExtension:No - } - #In case you have +#Mitigate issue with Variable Window Extension +Invoke-Command -ComputerName $MDTServer -ScriptBlock { + Wdsutil /Set-TransportServer /EnableTftpVariableWindowExtension:No +} + +#In case you have #endregion #region configure MDT Monitoring - Invoke-Command -ComputerName $MDTServer -ScriptBlock { - if (-not(get-module MicrosoftDeploymentToolkit)){ - Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" - } - if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ - New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "$using:DeploymentShareLocation" -Description "MDT Deployment Share" -NetworkPath "\\$using:MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose - } - #configure ports in MDT - Set-ItemProperty DS001:\ -name MonitorHost -value $using:MDTServer - #enable service - Enable-MDTMonitorService -EventPort 9800 -DataPort 9801 +Invoke-Command -ComputerName $MDTServer -ScriptBlock { + if (-not(get-module MicrosoftDeploymentToolkit)){ + Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" } + if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ + New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "$using:DeploymentShareLocation" -Description "MDT Deployment Share" -NetworkPath "\\$using:MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose + } + #configure ports in MDT + Set-ItemProperty DS001:\ -name MonitorHost -value $using:MDTServer + #enable service + Enable-MDTMonitorService -EventPort 9800 -DataPort 9801 +} - #add firewall rule - New-NetFirewallRule ` - -CimSession $MDTServer ` - -Action Allow ` - -Name "MDT-Monitoring-In-TCP" ` - -DisplayName "MDT (Monitoring-In-TCP)" ` - -Description "Inbound rule for MDT Monitoring. [TCP-9800,9801]" ` - -Enabled True ` - -Direction Inbound ` - -Program "System" ` - -Protocol UDP ` - -LocalPort 9800,9801 ` - -Profile Any ` - -Group "MDT" ` - -RemoteAddress Any +#add firewall rule +New-NetFirewallRule ` + -CimSession $MDTServer ` + -Action Allow ` + -Name "MDT-Monitoring-In-TCP" ` + -DisplayName "MDT (Monitoring-In-TCP)" ` + -Description "Inbound rule for MDT Monitoring. [TCP-9800,9801]" ` + -Enabled True ` + -Direction Inbound ` + -Program "System" ` + -Protocol UDP ` + -LocalPort 9800,9801 ` + -Profile Any ` + -Group "MDT" ` + -RemoteAddress Any #endregion #region replace customsettings.ini with all DB data to query (wizard output) - if ($Connection -eq "NamedPipes"){ - $Netlib="DBNMPNTW" - }elseif($Connection -eq "TCPIP"){ - $Netlib="DBMSSOCN" - $Creds=@" +if ($Connection -eq "NamedPipes"){ + $Netlib="DBNMPNTW" +}elseif($Connection -eq "TCPIP"){ + $Netlib="DBMSSOCN" + $Creds=@" DBID=MDTSQLUser DBPwd=LS1setup! "@ } - $content=@" +$content=@" [Settings] Priority=CSettings, CPackages, CApps, CAdmins, CRoles, Locations, LSettings, LPackages, LApps, LAdmins, LRoles, MMSettings, MMPackages, MMApps, MMAdmins, MMRoles, RSettings, RPackages, RApps, RAdmins, Default Properties=MyCustomProperty @@ -587,15 +587,16 @@ $text = [IO.File]::ReadAllText($CustomSettingsFile) -replace "`n", "`r`n" #endregion #region configure SQL to be able to access it remotely using MDTUser account(NamedPipes) or create dedicated SQL user (TCPIP) - #Add permissions for MDT account to sql database - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force - Install-Module -Name sqlserver -AllowClobber -Force - if ((Get-ExecutionPolicy) -eq "Restricted"){ - Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned - } - if ($Connection -eq "NamedPipes"){ - #Named Pipes - $sqlscript=@" +#Add permissions for MDT account to sql database +#note: added -TrustServerCertificate as I saw an error since 05/2023 when invoke-sqlcmd ran. "The certificate chain was issued by an authority that is not trusted..." +Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force +Install-Module -Name sqlserver -AllowClobber -Force +if ((Get-ExecutionPolicy) -eq "Restricted"){ + Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned +} +if ($Connection -eq "NamedPipes"){ + #Named Pipes +$sqlscript=@" USE [master] GO CREATE LOGIN [$env:userdomain\MDTUser] FROM WINDOWS WITH DEFAULT_DATABASE=[MDTDB] @@ -610,11 +611,11 @@ ALTER ROLE [db_datareader] ADD MEMBER [$env:userdomain\mdtuser] GO "@ - Invoke-Sqlcmd -ServerInstance $MDTServer\sqlexpress -Database MDTDB -Query $sqlscript +Invoke-Sqlcmd -ServerInstance $MDTServer\sqlexpress -Database MDTDB -Query $sqlscript -TrustServerCertificate }elseif($Connection -eq "TCPIP"){ - #TCP (add user and change authentication mode to be able to use both SQL and Windows Auth - $sqlscript=@" +#TCP (add user and change authentication mode to be able to use both SQL and Windows Auth +$sqlscript=@" USE [master] GO CREATE LOGIN [MDTSQLUser] WITH PASSWORD='LS1setup!', DEFAULT_DATABASE=[MDTDB] @@ -631,12 +632,12 @@ EXEC xp_instance_regwrite N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServe GO "@ - #TCP - Invoke-Sqlcmd -ServerInstance "tcp:$MDTServer" -Database MDTDB -Query $sqlscript - #restart service to apply mixed auth mode - Invoke-Command -ComputerName $MDTServer -scriptblock { - Restart-Service 'MSSQL$SQLEXPRESS' - } +#TCP +Invoke-Sqlcmd -ServerInstance "tcp:$MDTServer" -Database MDTDB -Query $sqlscript -TrustServerCertificate +#restart service to apply mixed auth mode +Invoke-Command -ComputerName $MDTServer -scriptblock { + Restart-Service 'MSSQL$SQLEXPRESS' +} } #endregion @@ -647,38 +648,38 @@ GO ##################################### #region Run from Hyper-V Host to create new, empty VMs - #some variables - $LabPrefix="MSLab20348.169-" - $vSwitchName="$($LabPrefix)LabSwitch" - $VMsPath="D:\MSLab20348.169\LAB\VMs" - $VMNames="AzHCI1","AzHCI2","AzHCI3","AzHCI4" - $NumberOfHDDs=4 - $SizeOfHDD=4TB - $MemoryStartupBytes=4GB - #create some blank VMs - foreach ($VMName in $VMNames){ - $VMName="$LabPrefix$VMName" - New-VM -Name $VMName -NewVHDPath "$VMsPath\$VMName\Virtual Hard Disks\$VMName.vhdx" -NewVHDSizeBytes 128GB -SwitchName $vSwitchName -Generation 2 -Path "$VMsPath" -MemoryStartupBytes $MemoryStartupBytes - 1..$NumberOfHDDs | ForEach-Object { - $VHD=New-VHD -Path "$VMsPath\$VMName\Virtual Hard Disks\HDD$_.vhdx" -SizeBytes $SizeOfHDD - Add-VMHardDiskDrive -VMName $VMName -Path "$VMsPath\$VMName\Virtual Hard Disks\HDD$_.vhdx" - } - #Add Adapter - Add-VMNetworkAdapter -VMName $VMName -SwitchName $vSwitchName - #configure Nested Virt and 2 cores - Set-VMProcessor -ExposeVirtualizationExtensions $true -VMName $VMName -Count 2 - #configure Memory - Set-VMMemory -VMName $VMName -DynamicMemoryEnabled $false - #configure network adapters - Set-VMNetworkAdapter -VMName $VMName -AllowTeaming On -MacAddressSpoofing On - Set-VMNetworkAdapterVlan -VMName $VMName -Trunk -NativeVlanId 0 -AllowedVlanIdList "1-10" - #disable automatic checkpoints - if ((get-vm -VMName $VMName).AutomaticCheckpointsEnabled -eq $True){ - Set-VM -Name $VMName -AutomaticCheckpointsEnabled $False - } - #Start VM - Start-VM -Name $VMName - } +#some variables +$LabPrefix="MSLab20348.169-" +$vSwitchName="$($LabPrefix)LabSwitch" +$VMsPath="D:\MSLab20348.169\LAB\VMs" +$VMNames="AzHCI1","AzHCI2","AzHCI3","AzHCI4" +$NumberOfHDDs=4 +$SizeOfHDD=4TB +$MemoryStartupBytes=4GB +#create some blank VMs +foreach ($VMName in $VMNames){ + $VMName="$LabPrefix$VMName" + New-VM -Name $VMName -NewVHDPath "$VMsPath\$VMName\Virtual Hard Disks\$VMName.vhdx" -NewVHDSizeBytes 128GB -SwitchName $vSwitchName -Generation 2 -Path "$VMsPath" -MemoryStartupBytes $MemoryStartupBytes + 1..$NumberOfHDDs | ForEach-Object { + $VHD=New-VHD -Path "$VMsPath\$VMName\Virtual Hard Disks\HDD$_.vhdx" -SizeBytes $SizeOfHDD + Add-VMHardDiskDrive -VMName $VMName -Path "$VMsPath\$VMName\Virtual Hard Disks\HDD$_.vhdx" + } + #Add Adapter + Add-VMNetworkAdapter -VMName $VMName -SwitchName $vSwitchName + #configure Nested Virt and 2 cores + Set-VMProcessor -ExposeVirtualizationExtensions $true -VMName $VMName -Count 2 + #configure Memory + Set-VMMemory -VMName $VMName -DynamicMemoryEnabled $false + #configure network adapters + Set-VMNetworkAdapter -VMName $VMName -AllowTeaming On -MacAddressSpoofing On + Set-VMNetworkAdapterVlan -VMName $VMName -Trunk -NativeVlanId 0 -AllowedVlanIdList "1-10" + #disable automatic checkpoints + if ((get-vm -VMName $VMName).AutomaticCheckpointsEnabled -eq $True){ + Set-VM -Name $VMName -AutomaticCheckpointsEnabled $False + } + #Start VM + Start-VM -Name $VMName +} #endregion @@ -687,123 +688,123 @@ GO ######################################## #region Create hash table out of machines that attempted boot last 5 minutes - #in real world scenairos you can have hash table like this: - <# +#in real world scenairos you can have hash table like this: +<# +$HVHosts = @() +$HVHosts+=@{ComputerName="AzSHCI1" ;IPAddress="10.0.0.101" ; MACAddress="00:15:5D:01:20:3B" ; GUID="FFB429BB-1521-47C6-88BF-F5F2D1BA17F5"} +$HVHosts+=@{ComputerName="AzSHCI2" ;IPAddress="10.0.0.102" ; MACAddress="00:15:5D:01:20:3E" ; GUID="056423D7-6BAC-460F-AD0D-78AF6D26E151"} +$HVHosts+=@{ComputerName="AzSHCI3" ;IPAddress="10.0.0.103" ; MACAddress="00:15:5D:01:20:40" ; GUID="D1DF2157-8EE0-4C7A-A0FD-6E7779C62FCE"} +$HVHosts+=@{ComputerName="AzSHCI4" ;IPAddress="10.0.0.104" ; MACAddress="00:15:5D:01:20:42" ; GUID="7392BBB5-99D7-4EEE-9C22-4CA6EB6058FB"} +#> + +#grab machines that attempted to boot in last 5 minutes and create hash table. +$HVHosts=Invoke-Command -ComputerName $MDTServer -ScriptBlock { + $IpaddressScope="10.0.0." + $IPAddressStart=101 #starting this number IPs will be asigned + $ServersNamePrefix="AzSHCI" + $events=Get-WinEvent -FilterHashtable @{LogName="Microsoft-Windows-Deployment-Services-Diagnostics/Operational";Id=4132;StartTime=(get-date).AddMinutes(-5)} | Where-Object Message -like "*it is not recognized*" | Sort-Object TimeCreated $HVHosts = @() - $HVHosts+=@{ComputerName="AzSHCI1" ;IPAddress="10.0.0.101" ; MACAddress="00:15:5D:01:20:3B" ; GUID="FFB429BB-1521-47C6-88BF-F5F2D1BA17F5"} - $HVHosts+=@{ComputerName="AzSHCI2" ;IPAddress="10.0.0.102" ; MACAddress="00:15:5D:01:20:3E" ; GUID="056423D7-6BAC-460F-AD0D-78AF6D26E151"} - $HVHosts+=@{ComputerName="AzSHCI3" ;IPAddress="10.0.0.103" ; MACAddress="00:15:5D:01:20:40" ; GUID="D1DF2157-8EE0-4C7A-A0FD-6E7779C62FCE"} - $HVHosts+=@{ComputerName="AzSHCI4" ;IPAddress="10.0.0.104" ; MACAddress="00:15:5D:01:20:42" ; GUID="7392BBB5-99D7-4EEE-9C22-4CA6EB6058FB"} - #> - - #grab machines that attempted to boot in last 5 minutes and create hash table. - $HVHosts=Invoke-Command -ComputerName $MDTServer -ScriptBlock { - $IpaddressScope="10.0.0." - $IPAddressStart=101 #starting this number IPs will be asigned - $ServersNamePrefix="AzSHCI" - $events=Get-WinEvent -FilterHashtable @{LogName="Microsoft-Windows-Deployment-Services-Diagnostics/Operational";Id=4132;StartTime=(get-date).AddMinutes(-5)} | Where-Object Message -like "*it is not recognized*" | Sort-Object TimeCreated - $HVHosts = @() - $GUIDS=@() - $i=1 - foreach ($event in $events){ - [System.Diagnostics.Eventing.Reader.EventLogRecord]$event=$event - if (!($guids).Contains($event.properties.value[2])){ - $HVHosts+= @{ ComputerName="$ServersNamePrefix$i";GUID = $event.properties.value[2] -replace '[{}]' ; MACAddress = $event.properties.value[0] -replace "-",":" ; IPAddress="$IpaddressScope$($IPAddressStart.tostring())"} - $i++ - $IPAddressStart++ - $GUIDS+=$event.properties.value[2] - } + $GUIDS=@() + $i=1 + foreach ($event in $events){ + [System.Diagnostics.Eventing.Reader.EventLogRecord]$event=$event + if (!($guids).Contains($event.properties.value[2])){ + $HVHosts+= @{ ComputerName="$ServersNamePrefix$i";GUID = $event.properties.value[2] -replace '[{}]' ; MACAddress = $event.properties.value[0] -replace "-",":" ; IPAddress="$IpaddressScope$($IPAddressStart.tostring())"} + $i++ + $IPAddressStart++ + $GUIDS+=$event.properties.value[2] } - Return $HVHosts } + Return $HVHosts +} $HVHosts #endregion #region create DHCP reservation for machines - #Create DHCP reservations for Hyper-V hosts - #Add DHCP Reservations - foreach ($HVHost in $HVHosts){ - if (!(Get-DhcpServerv4Reservation -ErrorAction SilentlyContinue -ComputerName $DHCPServer -ScopeId $ScopeID -ClientId ($HVHost.MACAddress).Replace(":","") | Where-Object IPAddress -eq $HVHost.IPAddress)){ - Add-DhcpServerv4Reservation -ComputerName $DHCPServer -ScopeId $ScopeID -IPAddress $HVHost.IPAddress -ClientId ($HVHost.MACAddress).Replace(":","") - } +#Create DHCP reservations for Hyper-V hosts + #Add DHCP Reservations + foreach ($HVHost in $HVHosts){ + if (!(Get-DhcpServerv4Reservation -ErrorAction SilentlyContinue -ComputerName $DHCPServer -ScopeId $ScopeID -ClientId ($HVHost.MACAddress).Replace(":","") | Where-Object IPAddress -eq $HVHost.IPAddress)){ + Add-DhcpServerv4Reservation -ComputerName $DHCPServer -ScopeId $ScopeID -IPAddress $HVHost.IPAddress -ClientId ($HVHost.MACAddress).Replace(":","") } + } - #configure NTP server in DHCP (might be useful if Servers have issues with time) - if (!(get-DhcpServerv4OptionValue -ComputerName $DHCPServer -ScopeId $ScopeID -OptionId 042 -ErrorAction SilentlyContinue)){ - Set-DhcpServerv4OptionValue -ComputerName $DHCPServer -ScopeId $ScopeID -OptionId 042 -Value "10.0.0.1" - } +#configure NTP server in DHCP (might be useful if Servers have issues with time) + if (!(get-DhcpServerv4OptionValue -ComputerName $DHCPServer -ScopeId $ScopeID -OptionId 042 -ErrorAction SilentlyContinue)){ + Set-DhcpServerv4OptionValue -ComputerName $DHCPServer -ScopeId $ScopeID -OptionId 042 -Value "10.0.0.1" + } #endregion #region add deploy info to AD Object and MDT Database - #download and unzip mdtdb (blog available in web.archive only https://web.archive.org/web/20190421025144/https://blogs.technet.microsoft.com/mniehaus/2009/05/14/manipulating-the-microsoft-deployment-toolkit-database-using-powershell/) - #Start-BitsTransfer -Source https://msdnshared.blob.core.windows.net/media/TNBlogsFS/prod.evol.blogs.technet.com/telligent.evolution.components.attachments/01/5209/00/00/03/24/15/04/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip - Start-BitsTransfer -Source https://github.com/microsoft/MSLab/raw/master/Scenarios/AzSHCI%20and%20MDT/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip +#download and unzip mdtdb (blog available in web.archive only https://web.archive.org/web/20190421025144/https://blogs.technet.microsoft.com/mniehaus/2009/05/14/manipulating-the-microsoft-deployment-toolkit-database-using-powershell/) +#Start-BitsTransfer -Source https://msdnshared.blob.core.windows.net/media/TNBlogsFS/prod.evol.blogs.technet.com/telligent.evolution.components.attachments/01/5209/00/00/03/24/15/04/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip +Start-BitsTransfer -Source https://github.com/microsoft/MSLab/raw/master/Scenarios/AzSHCI%20and%20MDT/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip - Expand-Archive -Path $env:USERPROFILE\Downloads\MDTDB.zip -DestinationPath $env:USERPROFILE\Downloads\MDTDB\ - if ((Get-ExecutionPolicy) -eq "Restricted"){ - Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned +Expand-Archive -Path $env:USERPROFILE\Downloads\MDTDB.zip -DestinationPath $env:USERPROFILE\Downloads\MDTDB\ +if ((Get-ExecutionPolicy) -eq "Restricted"){ + Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned +} +Import-Module $env:USERPROFILE\Downloads\MDTDB\MDTDB.psm1 +#make sure DS is connected + if (-not(get-module MicrosoftDeploymentToolkit)){ + Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" } - Import-Module $env:USERPROFILE\Downloads\MDTDB\MDTDB.psm1 - #make sure DS is connected - if (-not(get-module MicrosoftDeploymentToolkit)){ - Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" - } - if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ - New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose - } - #Connect to DB - #Connect-MDTDatabase -database mdtdb -sqlServer $MDTServer -instance SQLExpress - Connect-MDTDatabase -drivePath "DS001:\" + if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ + New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose + } +#Connect to DB + #Connect-MDTDatabase -database mdtdb -sqlServer $MDTServer -instance SQLExpress + Connect-MDTDatabase -drivePath "DS001:\" - #add hosts to MDT DB - foreach ($HVHost in $HVHosts){ - if (-not(Get-AdComputer -Filter "Name -eq `"$($HVHost.ComputerName)`"")){ - New-ADComputer -Name $hvhost.ComputerName - } - #add to MDT DB - if (-not (Get-MDTComputer -macAddress $HVHost.MACAddress)){ - New-MDTComputer -macAddress $HVHost.MACAddress -description $HVHost.ComputerName -uuid $HVHost.GUID -settings @{ - ComputerName = $HVHost.ComputerName - OSDComputerName = $HVHost.ComputerName - #SkipBDDWelcome = 'Yes' - } +#add hosts to MDT DB +foreach ($HVHost in $HVHosts){ + if (-not(Get-AdComputer -Filter "Name -eq `"$($HVHost.ComputerName)`"")){ + New-ADComputer -Name $hvhost.ComputerName + } + #add to MDT DB + if (-not (Get-MDTComputer -macAddress $HVHost.MACAddress)){ + New-MDTComputer -macAddress $HVHost.MACAddress -description $HVHost.ComputerName -uuid $HVHost.GUID -settings @{ + ComputerName = $HVHost.ComputerName + OSDComputerName = $HVHost.ComputerName + #SkipBDDWelcome = 'Yes' } - Get-MDTComputer -macAddress $HVHost.MACAddress | Set-MDTComputerRole -roles JoinDomain,AZSHCI } + Get-MDTComputer -macAddress $HVHost.MACAddress | Set-MDTComputerRole -roles JoinDomain,AZSHCI +} - #Configure MDT DB Roles - if (-not (Get-MDTRole -name azshci)){ - New-MDTRole -name AZSHCI -settings @{ - SkipTaskSequence = 'YES' - SkipWizard = 'YES' - SkipSummary = 'YES' - SkipApplications = 'YES' - TaskSequenceID = 'AZSHCI' - SkipFinalSummary = 'YES' - FinishAction = 'LOGOFF' - } +#Configure MDT DB Roles + if (-not (Get-MDTRole -name azshci)){ + New-MDTRole -name AZSHCI -settings @{ + SkipTaskSequence = 'YES' + SkipWizard = 'YES' + SkipSummary = 'YES' + SkipApplications = 'YES' + TaskSequenceID = 'AZSHCI' + SkipFinalSummary = 'YES' + FinishAction = 'LOGOFF' } + } - if (-not (Get-MDTRole -name JoinDomain)){ - New-MDTRole -name JoinDomain -settings @{ - SkipComputerName ='YES' - SkipDomainMembership='YES' - JoinDomain = $env:USERDNSDomain - DomainAdmin ='MDTUser' - DomainAdminDomain = $env:userdomain - DomainAdminPassword ='LS1setup!' - } + if (-not (Get-MDTRole -name JoinDomain)){ + New-MDTRole -name JoinDomain -settings @{ + SkipComputerName ='YES' + SkipDomainMembership='YES' + JoinDomain = $env:USERDNSDomain + DomainAdmin ='MDTUser' + DomainAdminDomain = $env:userdomain + DomainAdminPassword ='LS1setup!' } - - #allow machines to boot from PXE from DC by adding info into AD Object - foreach ($HVHost in $HVHosts){ - [guid]$guid=$HVHost.GUID - Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootGUID = $guid} - #Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootMachineFilePath = "DC"} } +#allow machines to boot from PXE from DC by adding info into AD Object +foreach ($HVHost in $HVHosts){ + [guid]$guid=$HVHost.GUID + Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootGUID = $guid} + #Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootMachineFilePath = "DC"} +} + #endregion ################################################ @@ -811,11 +812,11 @@ $HVHosts ################################################ #region remove pxe boot after install is done - foreach ($HVHost in $HVHosts){ - [guid]$guid=$HVHost.GUID - Set-ADComputer -identity $hvhost.ComputerName -remove @{netbootGUID = $guid} - Set-ADComputer -identity $hvhost.ComputerName -remove @{netbootMachineFilePath = "DC"} - } +foreach ($HVHost in $HVHosts){ + [guid]$guid=$HVHost.GUID + Set-ADComputer -identity $hvhost.ComputerName -remove @{netbootGUID = $guid} + Set-ADComputer -identity $hvhost.ComputerName -remove @{netbootMachineFilePath = "DC"} +} #endregion ############################################################################################################################################ @@ -835,59 +836,59 @@ $Headers=@{"Accept"="application/json"} $ContentType='application/json' function Ignore-SSLCertificates { - $Provider = New-Object Microsoft.CSharp.CSharpCodeProvider - $Compiler = $Provider.CreateCompiler() - $Params = New-Object System.CodeDom.Compiler.CompilerParameters - $Params.GenerateExecutable = $false - $Params.GenerateInMemory = $true - $Params.IncludeDebugInformation = $false - $Params.ReferencedAssemblies.Add("System.DLL") > $null - $TASource=@' - namespace Local.ToolkitExtensions.Net.CertificatePolicy +$Provider = New-Object Microsoft.CSharp.CSharpCodeProvider +$Compiler = $Provider.CreateCompiler() +$Params = New-Object System.CodeDom.Compiler.CompilerParameters +$Params.GenerateExecutable = $false +$Params.GenerateInMemory = $true +$Params.IncludeDebugInformation = $false +$Params.ReferencedAssemblies.Add("System.DLL") > $null +$TASource=@' + namespace Local.ToolkitExtensions.Net.CertificatePolicy + { + public class TrustAll : System.Net.ICertificatePolicy { - public class TrustAll : System.Net.ICertificatePolicy + public bool CheckValidationResult(System.Net.ServicePoint sp,System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem) { - public bool CheckValidationResult(System.Net.ServicePoint sp,System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem) - { - return true; - } + return true; } } + } '@ - $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource) - $TAAssembly=$TAResults.CompiledAssembly - $TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") - [System.Net.ServicePointManager]::CertificatePolicy = $TrustAll +$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource) +$TAAssembly=$TAResults.CompiledAssembly +$TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") +[System.Net.ServicePointManager]::CertificatePolicy = $TrustAll } #ignoring cert is needed for posh5. In 6 and newer you can just add -SkipCertificateCheck Ignore-SSLCertificates #reboot machines foreach ($idrac_ip in $idrac_ips){ - #Configure PXE for next reboot - $JsonBody = @{ Boot = @{ - "BootSourceOverrideTarget"="Pxe" - }} | ConvertTo-Json -Compress - $uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1" - Invoke-RestMethod -Body $JsonBody -Method Patch -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - - #Validate - $uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" - $Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - $Result.Boot.BootSourceOverrideTarget - - #check reboot options - #$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" - #$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - #$Result.Actions.'#ComputerSystem.Reset'.'ResetType@Redfish.AllowableValues' - - #reboot - #possible values: On,ForceOff,ForceRestart,GracefulShutdown,PushPowerButton,Nmi,PowerCycle - $JsonBody = @{ "ResetType" = "ForceRestart"} | ConvertTo-Json -Compress - $uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset" - Invoke-RestMethod -Body $JsonBody -Method Post -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - - Start-Sleep 10 +#Configure PXE for next reboot +$JsonBody = @{ Boot = @{ + "BootSourceOverrideTarget"="Pxe" + }} | ConvertTo-Json -Compress +$uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1" +Invoke-RestMethod -Body $JsonBody -Method Patch -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials + +#Validate +$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" +$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials +$Result.Boot.BootSourceOverrideTarget + +#check reboot options +#$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" +#$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials +#$Result.Actions.'#ComputerSystem.Reset'.'ResetType@Redfish.AllowableValues' + +#reboot +#possible values: On,ForceOff,ForceRestart,GracefulShutdown,PushPowerButton,Nmi,PowerCycle +$JsonBody = @{ "ResetType" = "ForceRestart"} | ConvertTo-Json -Compress +$uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset" +Invoke-RestMethod -Body $JsonBody -Method Post -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials + +Start-Sleep 10 } #endregion @@ -898,123 +899,118 @@ foreach ($idrac_ip in $idrac_ips){ ####################################################### #region Create hash table out of machines that attempted boot last 5 minutes - #in real world scenairos you can have hash table like this: - <# +#in real world scenairos you can have hash table like this: +<# +$HVHosts = @() +$HVHosts+=@{ComputerName="AxNode1" ;IPAddress="10.0.0.120" ; MACAddress="0C:42:A1:DD:57:DC" ; GUID="4C4C4544-004D-5410-8031-B4C04F373733"} +$HVHosts+=@{ComputerName="AxNode2" ;IPAddress="10.0.0.121" ; MACAddress="0C:42:A1:DD:57:C8" ; GUID="4C4C4544-004D-5410-8033-B4C04F373733"} +$HVHosts+=@{ComputerName="AxNode3" ;IPAddress="10.0.0.122" ; MACAddress="10:70:FD:08:E6:B4" ; GUID="4C4C4544-004D-5810-8046-B2C04F574D33"} +$HVHosts+=@{ComputerName="AxNode4" ;IPAddress="10.0.0.123" ; MACAddress="10:70:FD:08:E6:BC" ; GUID="4C4C4544-004D-5810-8046-B3C04F574D33"} +#> + +#grab machines that attempted to boot in last 5 minutes and create hash table. +$HVHosts=Invoke-Command -ComputerName $MDTServer -ScriptBlock { + $IpaddressScope="10.0.0." + $IPAddressStart=120 #starting this number IPs will be asigned + $ServersNamePrefix="AxNode" + $events=Get-WinEvent -FilterHashtable @{LogName="Microsoft-Windows-Deployment-Services-Diagnostics/Operational";Id=4132;StartTime=(get-date).AddMinutes(-5)} | Where-Object Message -like "*it is not recognized*" | Sort-Object TimeCreated $HVHosts = @() - $HVHosts+=@{ComputerName="AxNode1" ;IPAddress="10.0.0.120" ; MACAddress="0C:42:A1:DD:57:DC" ; GUID="4C4C4544-004D-5410-8031-B4C04F373733"} - $HVHosts+=@{ComputerName="AxNode2" ;IPAddress="10.0.0.121" ; MACAddress="0C:42:A1:DD:57:C8" ; GUID="4C4C4544-004D-5410-8033-B4C04F373733"} - $HVHosts+=@{ComputerName="AxNode3" ;IPAddress="10.0.0.122" ; MACAddress="10:70:FD:08:E6:B4" ; GUID="4C4C4544-004D-5810-8046-B2C04F574D33"} - $HVHosts+=@{ComputerName="AxNode4" ;IPAddress="10.0.0.123" ; MACAddress="10:70:FD:08:E6:BC" ; GUID="4C4C4544-004D-5810-8046-B3C04F574D33"} - #> - - #grab machines that attempted to boot in last 5 minutes and create hash table. - $HVHosts=Invoke-Command -ComputerName $MDTServer -ScriptBlock { - $IpaddressScope="10.0.0." - $IPAddressStart=120 #starting this number IPs will be asigned - $ServersNamePrefix="AxNode" - $events=Get-WinEvent -FilterHashtable @{LogName="Microsoft-Windows-Deployment-Services-Diagnostics/Operational";Id=4132;StartTime=(get-date).AddMinutes(-5)} | Where-Object Message -like "*it is not recognized*" | Sort-Object TimeCreated - $HVHosts = @() - $GUIDS=@() - $i=1 - foreach ($event in $events){ - [System.Diagnostics.Eventing.Reader.EventLogRecord]$event=$event - if (!($guids).Contains($event.properties.value[2])){ - $HVHosts+= @{ ComputerName="$ServersNamePrefix$i";GUID = $event.properties.value[2] -replace '[{}]' ; MACAddress = $event.properties.value[0] -replace "-",":" ; IPAddress="$IpaddressScope$($IPAddressStart.tostring())"} - $i++ - $IPAddressStart++ - $GUIDS+=$event.properties.value[2] - } + $GUIDS=@() + $i=1 + foreach ($event in $events){ + [System.Diagnostics.Eventing.Reader.EventLogRecord]$event=$event + if (!($guids).Contains($event.properties.value[2])){ + $HVHosts+= @{ ComputerName="$ServersNamePrefix$i";GUID = $event.properties.value[2] -replace '[{}]' ; MACAddress = $event.properties.value[0] -replace "-",":" ; IPAddress="$IpaddressScope$($IPAddressStart.tostring())"} + $i++ + $IPAddressStart++ + $GUIDS+=$event.properties.value[2] } - Return $HVHosts } - - + Return $HVHosts +} #endregion #region create DHCP reservation for machines - #Create DHCP reservations for Hyper-V hosts - #Add DHCP Reservations - foreach ($HVHost in $HVHosts){ - if (!(Get-DhcpServerv4Reservation -ErrorAction SilentlyContinue -ComputerName $DHCPServer -ScopeId $ScopeID -ClientId ($HVHost.MACAddress).Replace(":","") | Where-Object IPAddress -eq $HVHost.IPAddress)){ - Add-DhcpServerv4Reservation -ComputerName $DHCPServer -ScopeId $ScopeID -IPAddress $HVHost.IPAddress -ClientId ($HVHost.MACAddress).Replace(":","") - } - } +#Create DHCP reservations for Hyper-V hosts + #Add DHCP Reservations + foreach ($HVHost in $HVHosts){ + Add-DhcpServerv4Reservation -ComputerName $DHCPServer -ScopeId $ScopeID -IPAddress $HVHost.IPAddress -ClientId ($HVHost.MACAddress).Replace(":","") -ErrorAction Ignore + } + +#configure NTP server in DHCP (might be useful if Servers have issues with time) + Set-DhcpServerv4OptionValue -ComputerName $DHCPServer -ScopeId $ScopeID -OptionId 042 -Value "10.0.0.1" -ErrorAction Ignore - #configure NTP server in DHCP (might be useful if Servers have issues with time) - if (!(get-DhcpServerv4OptionValue -ComputerName $DHCPServer -ScopeId $ScopeID -OptionId 042 -ErrorAction SilentlyContinue)){ - Set-DhcpServerv4OptionValue -ComputerName $DHCPServer -ScopeId $ScopeID -OptionId 042 -Value "10.0.0.1" - } #endregion #region add deploy info to AD Object and MDT Database - #download and unzip mdtdb (blog available in web.archive only https://web.archive.org/web/20190421025144/https://blogs.technet.microsoft.com/mniehaus/2009/05/14/manipulating-the-microsoft-deployment-toolkit-database-using-powershell/) - #Start-BitsTransfer -Source https://msdnshared.blob.core.windows.net/media/TNBlogsFS/prod.evol.blogs.technet.com/telligent.evolution.components.attachments/01/5209/00/00/03/24/15/04/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip - Start-BitsTransfer -Source https://github.com/microsoft/MSLab/raw/master/Scenarios/AzSHCI%20and%20MDT/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip +#download and unzip mdtdb (blog available in web.archive only https://web.archive.org/web/20190421025144/https://blogs.technet.microsoft.com/mniehaus/2009/05/14/manipulating-the-microsoft-deployment-toolkit-database-using-powershell/) +#Start-BitsTransfer -Source https://msdnshared.blob.core.windows.net/media/TNBlogsFS/prod.evol.blogs.technet.com/telligent.evolution.components.attachments/01/5209/00/00/03/24/15/04/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip +Start-BitsTransfer -Source https://github.com/microsoft/MSLab/raw/master/Scenarios/AzSHCI%20and%20MDT/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip - Expand-Archive -Path $env:USERPROFILE\Downloads\MDTDB.zip -DestinationPath $env:USERPROFILE\Downloads\MDTDB\ - if ((Get-ExecutionPolicy) -eq "Restricted"){ - Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned +Expand-Archive -Path $env:USERPROFILE\Downloads\MDTDB.zip -DestinationPath $env:USERPROFILE\Downloads\MDTDB\ +if ((Get-ExecutionPolicy) -eq "Restricted"){ + Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned +} +Import-Module $env:USERPROFILE\Downloads\MDTDB\MDTDB.psm1 +#make sure DS is connected + if (-not(get-module MicrosoftDeploymentToolkit)){ + Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" } - Import-Module $env:USERPROFILE\Downloads\MDTDB\MDTDB.psm1 - #make sure DS is connected - if (-not(get-module MicrosoftDeploymentToolkit)){ - Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" - } - if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ - New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose - } - #Connect to DB - #Connect-MDTDatabase -database mdtdb -sqlServer $MDTServer -instance SQLExpress - Connect-MDTDatabase -drivePath "DS001:\" + if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ + New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose + } +#Connect to DB + #Connect-MDTDatabase -database mdtdb -sqlServer $MDTServer -instance SQLExpress + Connect-MDTDatabase -drivePath "DS001:\" - #add hosts to MDT DB - foreach ($HVHost in $HVHosts){ - if (-not(Get-AdComputer -Filter "Name -eq `"$($HVHost.ComputerName)`"")){ - New-ADComputer -Name $hvhost.ComputerName - } - #add to MDT DB - if (-not (Get-MDTComputer -macAddress $HVHost.MACAddress)){ - New-MDTComputer -macAddress $HVHost.MACAddress -description $HVHost.ComputerName -uuid $HVHost.GUID -settings @{ - ComputerName = $HVHost.ComputerName - OSDComputerName = $HVHost.ComputerName - #SkipBDDWelcome = 'Yes' - } +#add hosts to MDT DB +foreach ($HVHost in $HVHosts){ + if (-not(Get-AdComputer -Filter "Name -eq `"$($HVHost.ComputerName)`"")){ + New-ADComputer -Name $hvhost.ComputerName + } + #add to MDT DB + if (-not (Get-MDTComputer -macAddress $HVHost.MACAddress)){ + New-MDTComputer -macAddress $HVHost.MACAddress -description $HVHost.ComputerName -uuid $HVHost.GUID -settings @{ + ComputerName = $HVHost.ComputerName + OSDComputerName = $HVHost.ComputerName + #SkipBDDWelcome = 'Yes' } - Get-MDTComputer -macAddress $HVHost.MACAddress | Set-MDTComputerRole -roles JoinDomain,AZSHCI } + Get-MDTComputer -macAddress $HVHost.MACAddress | Set-MDTComputerRole -roles JoinDomain,AZSHCI +} - #Configure MDT DB Roles - if (-not (Get-MDTRole -name azshci)){ - New-MDTRole -name AZSHCI -settings @{ - SkipTaskSequence = 'YES' - SkipWizard = 'YES' - SkipSummary = 'YES' - SkipApplications = 'YES' - TaskSequenceID = 'AZSHCI' - SkipFinalSummary = 'YES' - FinishAction = 'LOGOFF' - } +#Configure MDT DB Roles + if (-not (Get-MDTRole -name azshci)){ + New-MDTRole -name AZSHCI -settings @{ + SkipTaskSequence = 'YES' + SkipWizard = 'YES' + SkipSummary = 'YES' + SkipApplications = 'YES' + TaskSequenceID = 'AZSHCI' + SkipFinalSummary = 'YES' + FinishAction = 'LOGOFF' } + } - if (-not (Get-MDTRole -name JoinDomain)){ - New-MDTRole -name JoinDomain -settings @{ - SkipComputerName ='YES' - SkipDomainMembership='YES' - JoinDomain = $env:USERDNSDomain - DomainAdmin ='MDTUser' - DomainAdminDomain = $env:userdomain - DomainAdminPassword ='LS1setup!' - } + if (-not (Get-MDTRole -name JoinDomain)){ + New-MDTRole -name JoinDomain -settings @{ + SkipComputerName ='YES' + SkipDomainMembership='YES' + JoinDomain = $env:USERDNSDomain + DomainAdmin ='MDTUser' + DomainAdminDomain = $env:userdomain + DomainAdminPassword ='LS1setup!' } - - #allow machines to boot from PXE from DC by adding info into AD Object - foreach ($HVHost in $HVHosts){ - [guid]$guid=$HVHost.GUID - Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootGUID = $guid} - #Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootMachineFilePath = "DC"} } +#allow machines to boot from PXE from DC by adding info into AD Object +foreach ($HVHost in $HVHosts){ + [guid]$guid=$HVHost.GUID + Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootGUID = $guid} + #Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootMachineFilePath = "DC"} +} + #endregion #region update task sequence with powershell script to install OS to smallest disk right before "New Computer only" group @@ -1023,11 +1019,11 @@ $PSScriptName="OSDDiskIndex.ps1" $PSScriptContent=@' $Disks=Get-CimInstance win32_DiskDrive if ($Disks.model -contains "DELLBOSS VD"){ - #exact model for Dell AX node (DELLBOSS VD) - $TSenv:OSDDiskIndex=($Disks | Where-Object Model -eq "DELLBOSS VD").Index +#exact model for Dell AX node (DELLBOSS VD) +$TSenv:OSDDiskIndex=($Disks | Where-Object Model -eq "DELLBOSS VD").Index }else{ - #or just smallest disk - $TSenv:OSDDiskIndex=($Disks | Where-Object MediaType -eq "Fixed hard disk media" | Sort-Object Size | Select-Object -First 1).Index +#or just smallest disk +$TSenv:OSDDiskIndex=($Disks | Where-Object MediaType -eq "Fixed hard disk media" | Sort-Object Size | Select-Object -First 1).Index } <# In case you need PowerShell and pause Task Sequence you can use this code: #source: http://wiki.wladik.net/windows/mdt/powershell-scripting @@ -1039,24 +1035,24 @@ Start PowerShell #> '@ - #update Tasksequence - $TS=Invoke-Command -ComputerName $MDTServer -ScriptBlock {Get-Content -Path $using:DeploymentShareLocation\Control\$using:TaskSequenceID\ts.xml} - $TextToSearch=' ' - $PoshScript=@" - - - $PSScriptName - - - - cscript.exe "%SCRIPTROOT%\ZTIPowerShell.wsf - +#update Tasksequence +$TS=Invoke-Command -ComputerName $MDTServer -ScriptBlock {Get-Content -Path $using:DeploymentShareLocation\Control\$using:TaskSequenceID\ts.xml} +$TextToSearch=' ' +$PoshScript=@" + + + $PSScriptName + + + + cscript.exe "%SCRIPTROOT%\ZTIPowerShell.wsf + $TextToSearch "@ - $NewTS=$TS.replace($TextToSearch,$PoshScript) - Invoke-Command -ComputerName $MDTServer -ScriptBlock {Set-Content -Path $using:DeploymentShareLocation\Control\$using:TaskSequenceID\ts.xml -Value $using:NewTS} - #insert script - Invoke-Command -ComputerName $MDTServer -ScriptBlock {Set-Content -Path $using:DeploymentShareLocation\Scripts\$using:PSScriptName -Value $using:PSScriptContent} +$NewTS=$TS.replace($TextToSearch,$PoshScript) +Invoke-Command -ComputerName $MDTServer -ScriptBlock {Set-Content -Path $using:DeploymentShareLocation\Control\$using:TaskSequenceID\ts.xml -Value $using:NewTS} +#insert script +Invoke-Command -ComputerName $MDTServer -ScriptBlock {Set-Content -Path $using:DeploymentShareLocation\Scripts\$using:PSScriptName -Value $using:PSScriptContent} #endregion @@ -1065,119 +1061,119 @@ $TextToSearch #Download DSU #https://github.com/DellProSupportGse/Tools/blob/main/DART.ps1 - #grab DSU links from Dell website - $URL="https://dl.dell.com/omimswac/dsu/" - $Results=Invoke-WebRequest $URL -UseDefaultCredentials - $Links=$results.Links.href | Select-Object -Skip 1 - #create PSObject from results - $DSUs=@() - foreach ($Link in $Links){ - $DSUs+=[PSCustomObject]@{ - Link = "https://dl.dell.com$Link" - Version = $link -split "_" | Select-Object -Last 2 | Select-Object -First 1 - } +#grab DSU links from Dell website +$URL="https://dl.dell.com/omimswac/dsu/" +$Results=Invoke-WebRequest $URL -UseDefaultCredentials +$Links=$results.Links.href | Select-Object -Skip 1 +#create PSObject from results +$DSUs=@() +foreach ($Link in $Links){ + $DSUs+=[PSCustomObject]@{ + Link = "https://dl.dell.com$Link" + Version = $link -split "_" | Select-Object -Last 2 | Select-Object -First 1 } - #download latest to separate folder - $LatestDSU=$DSUs | Sort-Object Version | Select-Object -Last 1 - $Folder="$env:USERPROFILE\Downloads\DSU" - if (-not (Test-Path $Folder)){New-Item -Path $Folder -ItemType Directory} - Start-BitsTransfer -Source $LatestDSU.Link -Destination $Folder\DSU.exe - - #add DSU as application to MDT - Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" - if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ - New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose - } - $AppName="Dell DSU $($LatestDSU.Version)" - Import-MDTApplication -path "DS001:\Applications" -enable "True" -Name $AppName -ShortName "DSU" -Version $LatestDSU.Version -Publisher "Dell" -Language "" -CommandLine "DSU.exe /silent" -WorkingDirectory ".\Applications\$AppName" -ApplicationSourcePath $Folder -DestinationFolder $AppName -Verbose - #grap package ID for role config - $DSUID=(Get-ChildItem -Path DS001:\Applications | Where-Object Name -eq $AppName).GUID +} +#download latest to separate folder +$LatestDSU=$DSUs | Sort-Object Version | Select-Object -Last 1 +$Folder="$env:USERPROFILE\Downloads\DSU" +if (-not (Test-Path $Folder)){New-Item -Path $Folder -ItemType Directory} +Start-BitsTransfer -Source $LatestDSU.Link -Destination $Folder\DSU.exe + +#add DSU as application to MDT +Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" +if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ + New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose +} +$AppName="Dell DSU $($LatestDSU.Version)" +Import-MDTApplication -path "DS001:\Applications" -enable "True" -Name $AppName -ShortName "DSU" -Version $LatestDSU.Version -Publisher "Dell" -Language "" -CommandLine "DSU.exe /silent" -WorkingDirectory ".\Applications\$AppName" -ApplicationSourcePath $Folder -DestinationFolder $AppName -Verbose +#grap package ID for role config +$DSUID=(Get-ChildItem -Path DS001:\Applications | Where-Object Name -eq $AppName).GUID #download catalog and create answer file to run DSU - #Dell Azure Stack HCI driver catalog https://downloads.dell.com/catalog/ASHCI-Catalog.xml.gz - #Download catalog - Start-BitsTransfer -Source "https://downloads.dell.com/catalog/ASHCI-Catalog.xml.gz" -Destination "$env:UserProfile\Downloads\ASHCI-Catalog.xml.gz" - #unzip gzip to a folder https://scatteredcode.net/download-and-extract-gzip-tar-with-powershell/ - $Folder="$env:USERPROFILE\Downloads\DSUPackage" - if (-not (Test-Path $Folder)){New-Item -Path $Folder -ItemType Directory} - Function Expand-GZipArchive{ - Param( - $infile, - $outfile = ($infile -replace '\.gz$','') - ) - $input = New-Object System.IO.FileStream $inFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) - $output = New-Object System.IO.FileStream $outFile, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None) - $gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress) - $buffer = New-Object byte[](1024) - while($true){ - $read = $gzipstream.Read($buffer, 0, 1024) - if ($read -le 0){break} - $output.Write($buffer, 0, $read) - } - $gzipStream.Close() - $output.Close() - $input.Close() - } - Expand-GZipArchive "$env:UserProfile\Downloads\ASHCI-Catalog.xml.gz" "$folder\ASHCI-Catalog.xml" - #create answerfile for DU - $content='@ - a - c - @' - Set-Content -Path "$folder\answer.txt" -Value $content -NoNewline - $content='"C:\Program Files\Dell\DELL EMC System Update\DSU.exe" --catalog-location=ASHCI-Catalog.xml --apply-upgrades <# - #Create output folder - $FolderName=$xml.manifest.version - New-Item -ItemType Directory -Name $FolderName -Path "$env:UserProfile\Downloads" -ErrorAction Ignore - New-Item -ItemType Directory -Name "RebootRequired" -Path "$env:UserProfile\Downloads\$FolderName" -ErrorAction Ignore - New-Item -ItemType Directory -Name "RebootNotRequired" -Path "$env:UserProfile\Downloads\$FolderName" -ErrorAction Ignore - #download files - foreach ($item in $xml.manifest.softwarecomponent){ - $filename=$($item.path.split("/")|Select-Object -Last 1) - if ($item.RebootRequired -eq "True"){ - Start-BitsTransfer -Source "https://downloads.dell.com/$($item.path)" -Destination "$env:UserProfile\Downloads\$FolderName\RebootRequired\$filename" -DisplayName "Downloading $filename releasedate $($item.releaseDate)" - }else{ - Start-BitsTransfer -Source "https://downloads.dell.com/$($item.path)" -Destination "$env:UserProfile\Downloads\$FolderName\RebootNotRequired\$filename" -DisplayName "Downloading $filename releasedate $($item.releaseDate)" - } +#Create output folder +$FolderName=$xml.manifest.version +New-Item -ItemType Directory -Name $FolderName -Path "$env:UserProfile\Downloads" -ErrorAction Ignore +New-Item -ItemType Directory -Name "RebootRequired" -Path "$env:UserProfile\Downloads\$FolderName" -ErrorAction Ignore +New-Item -ItemType Directory -Name "RebootNotRequired" -Path "$env:UserProfile\Downloads\$FolderName" -ErrorAction Ignore +#download files +foreach ($item in $xml.manifest.softwarecomponent){ + $filename=$($item.path.split("/")|Select-Object -Last 1) + if ($item.RebootRequired -eq "True"){ + Start-BitsTransfer -Source "https://downloads.dell.com/$($item.path)" -Destination "$env:UserProfile\Downloads\$FolderName\RebootRequired\$filename" -DisplayName "Downloading $filename releasedate $($item.releaseDate)" + }else{ + Start-BitsTransfer -Source "https://downloads.dell.com/$($item.path)" -Destination "$env:UserProfile\Downloads\$FolderName\RebootNotRequired\$filename" -DisplayName "Downloading $filename releasedate $($item.releaseDate)" } +} #> #endregion @@ -1196,57 +1192,57 @@ $Headers=@{"Accept"="application/json"} $ContentType='application/json' function Ignore-SSLCertificates { - $Provider = New-Object Microsoft.CSharp.CSharpCodeProvider - $Compiler = $Provider.CreateCompiler() - $Params = New-Object System.CodeDom.Compiler.CompilerParameters - $Params.GenerateExecutable = $false - $Params.GenerateInMemory = $true - $Params.IncludeDebugInformation = $false - $Params.ReferencedAssemblies.Add("System.DLL") > $null - $TASource=@' - namespace Local.ToolkitExtensions.Net.CertificatePolicy +$Provider = New-Object Microsoft.CSharp.CSharpCodeProvider +$Compiler = $Provider.CreateCompiler() +$Params = New-Object System.CodeDom.Compiler.CompilerParameters +$Params.GenerateExecutable = $false +$Params.GenerateInMemory = $true +$Params.IncludeDebugInformation = $false +$Params.ReferencedAssemblies.Add("System.DLL") > $null +$TASource=@' + namespace Local.ToolkitExtensions.Net.CertificatePolicy + { + public class TrustAll : System.Net.ICertificatePolicy { - public class TrustAll : System.Net.ICertificatePolicy + public bool CheckValidationResult(System.Net.ServicePoint sp,System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem) { - public bool CheckValidationResult(System.Net.ServicePoint sp,System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem) - { - return true; - } + return true; } } + } '@ - $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource) - $TAAssembly=$TAResults.CompiledAssembly - $TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") - [System.Net.ServicePointManager]::CertificatePolicy = $TrustAll +$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource) +$TAAssembly=$TAResults.CompiledAssembly +$TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") +[System.Net.ServicePointManager]::CertificatePolicy = $TrustAll } #ignoring cert is needed for posh5. In 6 and newer you can just add -SkipCertificateCheck Ignore-SSLCertificates #reboot machines foreach ($idrac_ip in $idrac_ips){ - #Configure PXE for next reboot - $JsonBody = @{ Boot = @{ - "BootSourceOverrideTarget"="Pxe" - }} | ConvertTo-Json -Compress - $uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1" - Invoke-RestMethod -Body $JsonBody -Method Patch -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - - #Validate - $uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" - $Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - $Result.Boot.BootSourceOverrideTarget - - #check reboot options - #$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" - #$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - #$Result.Actions.'#ComputerSystem.Reset'.'ResetType@Redfish.AllowableValues' - - #reboot - #possible values: On,ForceOff,ForceRestart,GracefulShutdown,PushPowerButton,Nmi,PowerCycle - $JsonBody = @{ "ResetType" = "ForceRestart"} | ConvertTo-Json -Compress - $uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset" - Invoke-RestMethod -Body $JsonBody -Method Post -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials +#Configure PXE for next reboot +$JsonBody = @{ Boot = @{ + "BootSourceOverrideTarget"="Pxe" + }} | ConvertTo-Json -Compress +$uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1" +Invoke-RestMethod -Body $JsonBody -Method Patch -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials + +#Validate +$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" +$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials +$Result.Boot.BootSourceOverrideTarget + +#check reboot options +#$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" +#$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials +#$Result.Actions.'#ComputerSystem.Reset'.'ResetType@Redfish.AllowableValues' + +#reboot +#possible values: On,ForceOff,ForceRestart,GracefulShutdown,PushPowerButton,Nmi,PowerCycle +$JsonBody = @{ "ResetType" = "ForceRestart"} | ConvertTo-Json -Compress +$uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset" +Invoke-RestMethod -Body $JsonBody -Method Post -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials } #endregion @@ -1258,9 +1254,9 @@ foreach ($idrac_ip in $idrac_ips){ #region remove pxe boot after install is done foreach ($HVHost in $HVHosts){ - [guid]$guid=$HVHost.GUID - Set-ADComputer -identity $hvhost.ComputerName -remove @{netbootGUID = $guid} - Set-ADComputer -identity $hvhost.ComputerName -remove @{netbootMachineFilePath = "DC"} +[guid]$guid=$HVHost.GUID +Set-ADComputer -identity $hvhost.ComputerName -remove @{netbootGUID = $guid} +Set-ADComputer -identity $hvhost.ComputerName -remove @{netbootMachineFilePath = "DC"} } #endregion @@ -1271,38 +1267,38 @@ foreach ($HVHost in $HVHosts){ ############################################################ #region Add Windows Server Task Sequence - $MDTServer="MDT" - if (-not(get-module MicrosoftDeploymentToolkit)){ - Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" - } +$MDTServer="MDT" +if (-not(get-module MicrosoftDeploymentToolkit)){ Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" - if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ - New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose - } +} +Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" +if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ + New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose +} - #Grab Server ISO - Write-Output "Please select ISO image with Windows Server 2022" - [reflection.assembly]::loadwithpartialname("System.Windows.Forms") - $openFile = New-Object System.Windows.Forms.OpenFileDialog -Property @{ - Title="Please select ISO image with Windows Server 2022" - } - $openFile.Filter = "iso files (*.iso)|*.iso|All files (*.*)|*.*" - If($openFile.ShowDialog() -eq "OK"){ - Write-Output "File $($openfile.FileName) selected" - } - if (!$openFile.FileName){ - Write-Error "Iso was not selected..." - } - $ISOServerPath=$openFile.FileName +#Grab Server ISO + Write-Output "Please select ISO image with Windows Server 2022" + [reflection.assembly]::loadwithpartialname("System.Windows.Forms") + $openFile = New-Object System.Windows.Forms.OpenFileDialog -Property @{ + Title="Please select ISO image with Windows Server 2022" + } + $openFile.Filter = "iso files (*.iso)|*.iso|All files (*.*)|*.*" + If($openFile.ShowDialog() -eq "OK"){ + Write-Output "File $($openfile.FileName) selected" + } + if (!$openFile.FileName){ + Write-Error "Iso was not selected..." + } + $ISOServerPath=$openFile.FileName - #Import Operating System - $ISO = Mount-DiskImage -ImagePath $ISOServerPath -PassThru - $ISOMediaPath = (Get-Volume -DiskImage $ISO).DriveLetter+':\' - Import-mdtoperatingsystem -path "DS001:\Operating Systems" -SourcePath $ISOMediaPath -DestinationFolder "Windows Server 2022 x64" -Verbose - $ISO | Dismount-DiskImage +#Import Operating System +$ISO = Mount-DiskImage -ImagePath $ISOServerPath -PassThru +$ISOMediaPath = (Get-Volume -DiskImage $ISO).DriveLetter+':\' +Import-mdtoperatingsystem -path "DS001:\Operating Systems" -SourcePath $ISOMediaPath -DestinationFolder "Windows Server 2022 x64" -Verbose +$ISO | Dismount-DiskImage - #add Task Sequence - import-mdttasksequence -path "DS001:\Task Sequences" -Name "Windows Server Deploy" -Template "Server.xml" -Comments "" -ID "WinSRV" -Version "1.0" -OperatingSystemPath "DS001:\Operating Systems\Windows Server 2022 SERVERDATACENTERCORE in Windows Server 2022 x64 install.wim" -FullName "PFE" -OrgName "Contoso" -HomePage "about:blank" -AdminPassword "LS1setup!" -Verbose +#add Task Sequence +import-mdttasksequence -path "DS001:\Task Sequences" -Name "Windows Server Deploy" -Template "Server.xml" -Comments "" -ID "WinSRV" -Version "1.0" -OperatingSystemPath "DS001:\Operating Systems\Windows Server 2022 SERVERDATACENTERCORE in Windows Server 2022 x64 install.wim" -FullName "PFE" -OrgName "Contoso" -HomePage "about:blank" -AdminPassword "LS1setup!" -Verbose #endregion @@ -1321,59 +1317,59 @@ $Headers=@{"Accept"="application/json"} $ContentType='application/json' function Ignore-SSLCertificates { - $Provider = New-Object Microsoft.CSharp.CSharpCodeProvider - $Compiler = $Provider.CreateCompiler() - $Params = New-Object System.CodeDom.Compiler.CompilerParameters - $Params.GenerateExecutable = $false - $Params.GenerateInMemory = $true - $Params.IncludeDebugInformation = $false - $Params.ReferencedAssemblies.Add("System.DLL") > $null - $TASource=@' - namespace Local.ToolkitExtensions.Net.CertificatePolicy +$Provider = New-Object Microsoft.CSharp.CSharpCodeProvider +$Compiler = $Provider.CreateCompiler() +$Params = New-Object System.CodeDom.Compiler.CompilerParameters +$Params.GenerateExecutable = $false +$Params.GenerateInMemory = $true +$Params.IncludeDebugInformation = $false +$Params.ReferencedAssemblies.Add("System.DLL") > $null +$TASource=@' + namespace Local.ToolkitExtensions.Net.CertificatePolicy + { + public class TrustAll : System.Net.ICertificatePolicy { - public class TrustAll : System.Net.ICertificatePolicy + public bool CheckValidationResult(System.Net.ServicePoint sp,System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem) { - public bool CheckValidationResult(System.Net.ServicePoint sp,System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem) - { - return true; - } + return true; } } + } '@ - $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource) - $TAAssembly=$TAResults.CompiledAssembly - $TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") - [System.Net.ServicePointManager]::CertificatePolicy = $TrustAll +$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource) +$TAAssembly=$TAResults.CompiledAssembly +$TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") +[System.Net.ServicePointManager]::CertificatePolicy = $TrustAll } #ignoring cert is needed for posh5. In 6 and newer you can just add -SkipCertificateCheck Ignore-SSLCertificates #reboot machines foreach ($idrac_ip in $idrac_ips){ - #Configure PXE for next reboot - $JsonBody = @{ Boot = @{ - "BootSourceOverrideTarget"="Pxe" - }} | ConvertTo-Json -Compress - $uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1" - Invoke-RestMethod -Body $JsonBody -Method Patch -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - - #Validate - $uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" - $Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - $Result.Boot.BootSourceOverrideTarget - - #check reboot options - #$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" - #$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - #$Result.Actions.'#ComputerSystem.Reset'.'ResetType@Redfish.AllowableValues' - - #reboot - #possible values: On,ForceOff,ForceRestart,GracefulShutdown,PushPowerButton,Nmi,PowerCycle - $JsonBody = @{ "ResetType" = "ForceRestart"} | ConvertTo-Json -Compress - $uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset" - Invoke-RestMethod -Body $JsonBody -Method Post -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - - Start-Sleep 10 +#Configure PXE for next reboot +$JsonBody = @{ Boot = @{ + "BootSourceOverrideTarget"="Pxe" + }} | ConvertTo-Json -Compress +$uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1" +Invoke-RestMethod -Body $JsonBody -Method Patch -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials + +#Validate +$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" +$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials +$Result.Boot.BootSourceOverrideTarget + +#check reboot options +#$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" +#$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials +#$Result.Actions.'#ComputerSystem.Reset'.'ResetType@Redfish.AllowableValues' + +#reboot +#possible values: On,ForceOff,ForceRestart,GracefulShutdown,PushPowerButton,Nmi,PowerCycle +$JsonBody = @{ "ResetType" = "ForceRestart"} | ConvertTo-Json -Compress +$uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset" +Invoke-RestMethod -Body $JsonBody -Method Post -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials + +Start-Sleep 10 } #endregion @@ -1384,121 +1380,121 @@ foreach ($idrac_ip in $idrac_ips){ ####################################################### #region Create hash table out of machines that attempted boot last 5 minutes - #in real world scenairos you can have hash table like this: - <# +#in real world scenairos you can have hash table like this: +<# +$HVHosts = @() +$HVHosts+=@{ComputerName="R440Node1" ;IPAddress="10.0.0.131" ; MACAddress="34:80:0D:91:0B:66" ; GUID="4C4C4544-0051-5610-8056-B8C04F323333"} +$HVHosts+=@{ComputerName="R440Node2" ;IPAddress="10.0.0.132" ; MACAddress="34:80:0D:91:0B:54" ; GUID="4C4C4544-0051-5610-8054-B8C04F323333"} +#> + +#grab machines that attempted to boot in last 5 minutes and create hash table. +$HVHosts=Invoke-Command -ComputerName $MDTServer -ScriptBlock { + $IpaddressScope="10.0.0." + $IPAddressStart=122 #starting this number IPs will be asigned + $ServersNamePrefix="R440Node" + $events=Get-WinEvent -FilterHashtable @{LogName="Microsoft-Windows-Deployment-Services-Diagnostics/Operational";Id=4132;StartTime=(get-date).AddMinutes(-5)} | Where-Object Message -like "*it is not recognized*" | Sort-Object TimeCreated $HVHosts = @() - $HVHosts+=@{ComputerName="R440Node1" ;IPAddress="10.0.0.131" ; MACAddress="34:80:0D:91:0B:66" ; GUID="4C4C4544-0051-5610-8056-B8C04F323333"} - $HVHosts+=@{ComputerName="R440Node2" ;IPAddress="10.0.0.132" ; MACAddress="34:80:0D:91:0B:54" ; GUID="4C4C4544-0051-5610-8054-B8C04F323333"} - #> - - #grab machines that attempted to boot in last 5 minutes and create hash table. - $HVHosts=Invoke-Command -ComputerName $MDTServer -ScriptBlock { - $IpaddressScope="10.0.0." - $IPAddressStart=122 #starting this number IPs will be asigned - $ServersNamePrefix="R440Node" - $events=Get-WinEvent -FilterHashtable @{LogName="Microsoft-Windows-Deployment-Services-Diagnostics/Operational";Id=4132;StartTime=(get-date).AddMinutes(-5)} | Where-Object Message -like "*it is not recognized*" | Sort-Object TimeCreated - $HVHosts = @() - $GUIDS=@() - $i=1 - foreach ($event in $events){ - [System.Diagnostics.Eventing.Reader.EventLogRecord]$event=$event - if (!($guids).Contains($event.properties.value[2])){ - $HVHosts+= @{ ComputerName="$ServersNamePrefix$i";GUID = $event.properties.value[2] -replace '[{}]' ; MACAddress = $event.properties.value[0] -replace "-",":" ; IPAddress="$IpaddressScope$($IPAddressStart.tostring())"} - $i++ - $IPAddressStart++ - $GUIDS+=$event.properties.value[2] - } + $GUIDS=@() + $i=1 + foreach ($event in $events){ + [System.Diagnostics.Eventing.Reader.EventLogRecord]$event=$event + if (!($guids).Contains($event.properties.value[2])){ + $HVHosts+= @{ ComputerName="$ServersNamePrefix$i";GUID = $event.properties.value[2] -replace '[{}]' ; MACAddress = $event.properties.value[0] -replace "-",":" ; IPAddress="$IpaddressScope$($IPAddressStart.tostring())"} + $i++ + $IPAddressStart++ + $GUIDS+=$event.properties.value[2] } - Return $HVHosts } + Return $HVHosts +} #endregion #region create DHCP reservation for machines - #Create DHCP reservations for Hyper-V hosts - #Add DHCP Reservations - foreach ($HVHost in $HVHosts){ - if (!(Get-DhcpServerv4Reservation -ErrorAction SilentlyContinue -ComputerName $DHCPServer -ScopeId $ScopeID -ClientId ($HVHost.MACAddress).Replace(":","") | Where-Object IPAddress -eq $HVHost.IPAddress)){ - Add-DhcpServerv4Reservation -ComputerName $DHCPServer -ScopeId $ScopeID -IPAddress $HVHost.IPAddress -ClientId ($HVHost.MACAddress).Replace(":","") - } +#Create DHCP reservations for Hyper-V hosts + #Add DHCP Reservations + foreach ($HVHost in $HVHosts){ + if (!(Get-DhcpServerv4Reservation -ErrorAction SilentlyContinue -ComputerName $DHCPServer -ScopeId $ScopeID -ClientId ($HVHost.MACAddress).Replace(":","") | Where-Object IPAddress -eq $HVHost.IPAddress)){ + Add-DhcpServerv4Reservation -ComputerName $DHCPServer -ScopeId $ScopeID -IPAddress $HVHost.IPAddress -ClientId ($HVHost.MACAddress).Replace(":","") } + } - #configure NTP server in DHCP (might be useful if Servers have issues with time) - if (!(get-DhcpServerv4OptionValue -ComputerName $DHCPServer -ScopeId $ScopeID -OptionId 042 -ErrorAction SilentlyContinue)){ - Set-DhcpServerv4OptionValue -ComputerName $DHCPServer -ScopeId $ScopeID -OptionId 042 -Value "10.0.0.1" - } +#configure NTP server in DHCP (might be useful if Servers have issues with time) + if (!(get-DhcpServerv4OptionValue -ComputerName $DHCPServer -ScopeId $ScopeID -OptionId 042 -ErrorAction SilentlyContinue)){ + Set-DhcpServerv4OptionValue -ComputerName $DHCPServer -ScopeId $ScopeID -OptionId 042 -Value "10.0.0.1" + } #endregion #region add deploy info to AD Object and MDT Database - #download and unzip mdtdb (blog available in web.archive only https://web.archive.org/web/20190421025144/https://blogs.technet.microsoft.com/mniehaus/2009/05/14/manipulating-the-microsoft-deployment-toolkit-database-using-powershell/) - #Start-BitsTransfer -Source https://msdnshared.blob.core.windows.net/media/TNBlogsFS/prod.evol.blogs.technet.com/telligent.evolution.components.attachments/01/5209/00/00/03/24/15/04/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip - Start-BitsTransfer -Source https://github.com/microsoft/MSLab/raw/master/Scenarios/AzSHCI%20and%20MDT/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip +#download and unzip mdtdb (blog available in web.archive only https://web.archive.org/web/20190421025144/https://blogs.technet.microsoft.com/mniehaus/2009/05/14/manipulating-the-microsoft-deployment-toolkit-database-using-powershell/) +#Start-BitsTransfer -Source https://msdnshared.blob.core.windows.net/media/TNBlogsFS/prod.evol.blogs.technet.com/telligent.evolution.components.attachments/01/5209/00/00/03/24/15/04/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip +Start-BitsTransfer -Source https://github.com/microsoft/MSLab/raw/master/Scenarios/AzSHCI%20and%20MDT/MDTDB.zip -Destination $env:USERPROFILE\Downloads\MDTDB.zip - Expand-Archive -Path $env:USERPROFILE\Downloads\MDTDB.zip -DestinationPath $env:USERPROFILE\Downloads\MDTDB\ -Force - if ((Get-ExecutionPolicy) -eq "Restricted"){ - Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned +Expand-Archive -Path $env:USERPROFILE\Downloads\MDTDB.zip -DestinationPath $env:USERPROFILE\Downloads\MDTDB\ -Force +if ((Get-ExecutionPolicy) -eq "Restricted"){ + Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned +} +Import-Module $env:USERPROFILE\Downloads\MDTDB\MDTDB.psm1 +#make sure DS is connected + if (-not(get-module MicrosoftDeploymentToolkit)){ + Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" } - Import-Module $env:USERPROFILE\Downloads\MDTDB\MDTDB.psm1 - #make sure DS is connected - if (-not(get-module MicrosoftDeploymentToolkit)){ - Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" - } - if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ - New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose - } - #Connect to DB - #Connect-MDTDatabase -database mdtdb -sqlServer $MDTServer -instance SQLExpress - Connect-MDTDatabase -drivePath "DS001:\" + if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ + New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose + } +#Connect to DB + #Connect-MDTDatabase -database mdtdb -sqlServer $MDTServer -instance SQLExpress + Connect-MDTDatabase -drivePath "DS001:\" - #add hosts to MDT DB - foreach ($HVHost in $HVHosts){ - if (-not(Get-AdComputer -Filter "Name -eq `"$($HVHost.ComputerName)`"")){ - New-ADComputer -Name $hvhost.ComputerName - } - #add to MDT DB - if (-not (Get-MDTComputer -macAddress $HVHost.MACAddress)){ - New-MDTComputer -macAddress $HVHost.MACAddress -description $HVHost.ComputerName -uuid $HVHost.GUID -settings @{ - ComputerName = $HVHost.ComputerName - OSDComputerName = $HVHost.ComputerName - #SkipBDDWelcome = 'Yes' - } +#add hosts to MDT DB +foreach ($HVHost in $HVHosts){ + if (-not(Get-AdComputer -Filter "Name -eq `"$($HVHost.ComputerName)`"")){ + New-ADComputer -Name $hvhost.ComputerName + } + #add to MDT DB + if (-not (Get-MDTComputer -macAddress $HVHost.MACAddress)){ + New-MDTComputer -macAddress $HVHost.MACAddress -description $HVHost.ComputerName -uuid $HVHost.GUID -settings @{ + ComputerName = $HVHost.ComputerName + OSDComputerName = $HVHost.ComputerName + #SkipBDDWelcome = 'Yes' } - Get-MDTComputer -macAddress $HVHost.MACAddress | Set-MDTComputerRole -roles JoinDomain,WinSRV } + Get-MDTComputer -macAddress $HVHost.MACAddress | Set-MDTComputerRole -roles JoinDomain,WinSRV +} - #Configure MDT DB Roles - if (-not (Get-MDTRole -name WinSRV)){ - New-MDTRole -name WinSRV -settings @{ - SkipTaskSequence = 'YES' - SkipWizard = 'YES' - SkipSummary = 'YES' - SkipApplications = 'YES' - TaskSequenceID = 'WinSRV' - SkipFinalSummary = 'YES' - FinishAction = 'LOGOFF' - } +#Configure MDT DB Roles + if (-not (Get-MDTRole -name WinSRV)){ + New-MDTRole -name WinSRV -settings @{ + SkipTaskSequence = 'YES' + SkipWizard = 'YES' + SkipSummary = 'YES' + SkipApplications = 'YES' + TaskSequenceID = 'WinSRV' + SkipFinalSummary = 'YES' + FinishAction = 'LOGOFF' } + } - if (-not (Get-MDTRole -name JoinDomain)){ - New-MDTRole -name JoinDomain -settings @{ - SkipComputerName ='YES' - SkipDomainMembership='YES' - JoinDomain = $env:USERDNSDomain - DomainAdmin ='MDTUser' - DomainAdminDomain = $env:userdomain - DomainAdminPassword ='LS1setup!' - } + if (-not (Get-MDTRole -name JoinDomain)){ + New-MDTRole -name JoinDomain -settings @{ + SkipComputerName ='YES' + SkipDomainMembership='YES' + JoinDomain = $env:USERDNSDomain + DomainAdmin ='MDTUser' + DomainAdminDomain = $env:userdomain + DomainAdminPassword ='LS1setup!' } - - #allow machines to boot from PXE from DC by adding info into AD Object - foreach ($HVHost in $HVHosts){ - [guid]$guid=$HVHost.GUID - Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootGUID = $guid} - #Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootMachineFilePath = "DC"} } +#allow machines to boot from PXE from DC by adding info into AD Object +foreach ($HVHost in $HVHosts){ + [guid]$guid=$HVHost.GUID + Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootGUID = $guid} + #Set-ADComputer -identity $hvhost.ComputerName -replace @{netbootMachineFilePath = "DC"} +} + #endregion #region update task sequence with powershell script to install OS to smallest disk right before "New Computer only" group @@ -1507,11 +1503,11 @@ $PSScriptName="OSDDiskIndex.ps1" $PSScriptContent=@' $Disks=Get-CimInstance win32_DiskDrive if ($Disks.model -contains "DELLBOSS VD"){ - #exact model for Dell AX node (DELLBOSS VD) - $TSenv:OSDDiskIndex=($Disks | Where-Object Model -eq "DELLBOSS VD").Index +#exact model for Dell AX node (DELLBOSS VD) +$TSenv:OSDDiskIndex=($Disks | Where-Object Model -eq "DELLBOSS VD").Index }else{ - #or just smallest disk - $TSenv:OSDDiskIndex=($Disks | Where-Object MediaType -eq "Fixed hard disk media" | Sort-Object Size | Select-Object -First 1).Index +#or just smallest disk +$TSenv:OSDDiskIndex=($Disks | Where-Object MediaType -eq "Fixed hard disk media" | Sort-Object Size | Select-Object -First 1).Index } <# In case you need PowerShell and pause Task Sequence you can use this code: #source: http://wiki.wladik.net/windows/mdt/powershell-scripting @@ -1523,128 +1519,128 @@ Start PowerShell #> '@ - #update Tasksequence - $TS=Invoke-Command -ComputerName $MDTServer -ScriptBlock {Get-Content -Path $using:DeploymentShareLocation\Control\$using:TaskSequenceID\ts.xml} - $TextToSearch=' ' - $PoshScript=@" - - - $PSScriptName - - - - cscript.exe "%SCRIPTROOT%\ZTIPowerShell.wsf - +#update Tasksequence +$TS=Invoke-Command -ComputerName $MDTServer -ScriptBlock {Get-Content -Path $using:DeploymentShareLocation\Control\$using:TaskSequenceID\ts.xml} +$TextToSearch=' ' +$PoshScript=@" + + + $PSScriptName + + + + cscript.exe "%SCRIPTROOT%\ZTIPowerShell.wsf + $TextToSearch "@ - $NewTS=$TS.replace($TextToSearch,$PoshScript) - Invoke-Command -ComputerName $MDTServer -ScriptBlock {Set-Content -Path $using:DeploymentShareLocation\Control\$using:TaskSequenceID\ts.xml -Value $using:NewTS} - #insert script - Invoke-Command -ComputerName $MDTServer -ScriptBlock {Set-Content -Path $using:DeploymentShareLocation\Scripts\$using:PSScriptName -Value $using:PSScriptContent} +$NewTS=$TS.replace($TextToSearch,$PoshScript) +Invoke-Command -ComputerName $MDTServer -ScriptBlock {Set-Content -Path $using:DeploymentShareLocation\Control\$using:TaskSequenceID\ts.xml -Value $using:NewTS} +#insert script +Invoke-Command -ComputerName $MDTServer -ScriptBlock {Set-Content -Path $using:DeploymentShareLocation\Scripts\$using:PSScriptName -Value $using:PSScriptContent} #endregion #region update task sequence with drivers $RoleName="AXNodeDrivers" if (-not (Get-MDTRole -name $RoleName)){ - #Download DSU - #https://github.com/DellProSupportGse/Tools/blob/main/DART.ps1 - - #grab DSU links from Dell website - $URL="https://dl.dell.com/omimswac/dsu/" - $Results=Invoke-WebRequest $URL -UseDefaultCredentials - $Links=$results.Links.href | Select-Object -Skip 1 - #create PSObject from results - $DSUs=@() - foreach ($Link in $Links){ - $DSUs+=[PSCustomObject]@{ - Link = "https://dl.dell.com$Link" - Version = $link -split "_" | Select-Object -Last 2 | Select-Object -First 1 - } - } - #download latest to separate folder - $LatestDSU=$DSUs | Sort-Object Version | Select-Object -Last 1 - $Folder="$env:USERPROFILE\Downloads\DSU" - if (-not (Test-Path $Folder)){New-Item -Path $Folder -ItemType Directory} - Start-BitsTransfer -Source $LatestDSU.Link -Destination $Folder\DSU.exe +#Download DSU +#https://github.com/DellProSupportGse/Tools/blob/main/DART.ps1 - #add DSU as application to MDT - Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1" - if (-not(Get-PSDrive -Name ds001 -ErrorAction Ignore)){ - New-PSDrive -Name "DS001" -PSProvider "MDTProvider" -Root "\\$MDTServer\DeploymentShare$" -Description "MDT Deployment Share" -NetworkPath "\\$MDTServer\DeploymentShare$" -Verbose | add-MDTPersistentDrive -Verbose - } - $AppName="Dell DSU $($LatestDSU.Version)" - Import-MDTApplication -path "DS001:\Applications" -enable "True" -Name $AppName -ShortName "DSU" -Version $LatestDSU.Version -Publisher "Dell" -Language "" -CommandLine "DSU.exe /silent" -WorkingDirectory ".\Applications\$AppName" -ApplicationSourcePath $Folder -DestinationFolder $AppName -Verbose - #grap package ID for role config - $DSUID=(Get-ChildItem -Path DS001:\Applications | Where-Object Name -eq $AppName).GUID - - #download catalog and create answer file to run DSU - #Dell Azure Stack HCI driver catalog https://downloads.dell.com/catalog/ASHCI-Catalog.xml.gz - #Download catalog - Start-BitsTransfer -Source "https://downloads.dell.com/catalog/ASHCI-Catalog.xml.gz" -Destination "$env:UserProfile\Downloads\ASHCI-Catalog.xml.gz" - #unzip gzip to a folder https://scatteredcode.net/download-and-extract-gzip-tar-with-powershell/ - $Folder="$env:USERPROFILE\Downloads\DSUPackage" - if (-not (Test-Path $Folder)){New-Item -Path $Folder -ItemType Directory} - Function Expand-GZipArchive{ - Param( - $infile, - $outfile = ($infile -replace '\.gz$','') - ) - $input = New-Object System.IO.FileStream $inFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) - $output = New-Object System.IO.FileStream $outFile, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None) - $gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress) - $buffer = New-Object byte[](1024) - while($true){ - $read = $gzipstream.Read($buffer, 0, 1024) - if ($read -le 0){break} - $output.Write($buffer, 0, $read) - } - $gzipStream.Close() - $output.Close() - $input.Close() - } - Expand-GZipArchive "$env:UserProfile\Downloads\ASHCI-Catalog.xml.gz" "$folder\ASHCI-Catalog.xml" - #create answerfile for DU - $content='@ - a - c - @' - Set-Content -Path "$folder\answer.txt" -Value $content -NoNewline - $content='"C:\Program Files\Dell\DELL EMC System Update\DSU.exe" --catalog-location=ASHCI-Catalog.xml --apply-upgrades $null - $TASource=@' - namespace Local.ToolkitExtensions.Net.CertificatePolicy +$Provider = New-Object Microsoft.CSharp.CSharpCodeProvider +$Compiler = $Provider.CreateCompiler() +$Params = New-Object System.CodeDom.Compiler.CompilerParameters +$Params.GenerateExecutable = $false +$Params.GenerateInMemory = $true +$Params.IncludeDebugInformation = $false +$Params.ReferencedAssemblies.Add("System.DLL") > $null +$TASource=@' + namespace Local.ToolkitExtensions.Net.CertificatePolicy + { + public class TrustAll : System.Net.ICertificatePolicy { - public class TrustAll : System.Net.ICertificatePolicy + public bool CheckValidationResult(System.Net.ServicePoint sp,System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem) { - public bool CheckValidationResult(System.Net.ServicePoint sp,System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem) - { - return true; - } + return true; } } + } '@ - $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource) - $TAAssembly=$TAResults.CompiledAssembly - $TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") - [System.Net.ServicePointManager]::CertificatePolicy = $TrustAll +$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource) +$TAAssembly=$TAResults.CompiledAssembly +$TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") +[System.Net.ServicePointManager]::CertificatePolicy = $TrustAll } #ignoring cert is needed for posh5. In 6 and newer you can just add -SkipCertificateCheck Ignore-SSLCertificates #reboot machines foreach ($idrac_ip in $idrac_ips){ - #Configure PXE for next reboot - $JsonBody = @{ Boot = @{ - "BootSourceOverrideTarget"="Pxe" - }} | ConvertTo-Json -Compress - $uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1" - Invoke-RestMethod -Body $JsonBody -Method Patch -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - - #Validate - $uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" - $Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - $Result.Boot.BootSourceOverrideTarget - - #check reboot options - #$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" - #$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials - #$Result.Actions.'#ComputerSystem.Reset'.'ResetType@Redfish.AllowableValues' - - #reboot - #possible values: On,ForceOff,ForceRestart,GracefulShutdown,PushPowerButton,Nmi,PowerCycle - $JsonBody = @{ "ResetType" = "ForceRestart"} | ConvertTo-Json -Compress - $uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset" - Invoke-RestMethod -Body $JsonBody -Method Post -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials +#Configure PXE for next reboot +$JsonBody = @{ Boot = @{ + "BootSourceOverrideTarget"="Pxe" + }} | ConvertTo-Json -Compress +$uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1" +Invoke-RestMethod -Body $JsonBody -Method Patch -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials + +#Validate +$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" +$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials +$Result.Boot.BootSourceOverrideTarget + +#check reboot options +#$uri="https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/" +#$Result=Invoke-RestMethod -Method Get -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials +#$Result.Actions.'#ComputerSystem.Reset'.'ResetType@Redfish.AllowableValues' + +#reboot +#possible values: On,ForceOff,ForceRestart,GracefulShutdown,PushPowerButton,Nmi,PowerCycle +$JsonBody = @{ "ResetType" = "ForceRestart"} | ConvertTo-Json -Compress +$uri = "https://$idrac_ip/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset" +Invoke-RestMethod -Body $JsonBody -Method Post -ContentType $ContentType -Headers $Headers -Uri $uri -Credential $Credentials } -#endregion - +#endregion \ No newline at end of file diff --git a/Scenarios/AzSHCI and Migration from Windows Server/Scenario.ps1 b/Scenarios/AzSHCI and Migration from Windows Server/Scenario.ps1 index 11247e64..af12bf13 100644 --- a/Scenarios/AzSHCI and Migration from Windows Server/Scenario.ps1 +++ b/Scenarios/AzSHCI and Migration from Windows Server/Scenario.ps1 @@ -163,6 +163,7 @@ $DestinationClusterName="AzSHCI-Cluster" $SourceStoragePath="C:\ClusterStorage\CSV1" $DestinationStoragePath="C:\ClusterStorage\CSV1" + $DestinationSwitchName=(Get-VMSwitch -CimSession ((Get-ClusterNode -Cluster $ClusterName).Name | Select-Object -First 1)).Name $VMNames=(Get-VM -cimsession (get-clusternode -cluster $SourceClusterName).Name | Where-Object Path -Like "$SourceStoragePath*").Name # Temporarily enable CredSSP delegation to avoid double-hop issue @@ -178,27 +179,42 @@ #do the move foreach ($VMName in $VMNames){ #remove VM from HA Resources + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Removing VM $($VM.VMName) From Cluster resources" Get-ClusterResource -Cluster $SourceClusterName -name "Virtual Machine $VMName" -ErrorAction Ignore | Remove-ClusterResource -force Get-ClusterGroup -Cluster $SourceClusterName -Name $VMName -ErrorAction Ignore | Remove-ClusterGroup -force - #Grab random node in cluster $DestinationClusterName + #Grab random node in cluster $DestinationClusterName (does not have to be random of course) $VM=Get-VM -Cimsession (get-clusternode -cluster $SourceClusterName).Name -Name $VMName - $VM | Stop-VM -Save + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Stopping VM $($VM.VMName)" + $VM | Stop-VM + #or just save it, but we need to update version anyway + #$VM | Stop-VM -Save + #If there is different switch name in destination node, you should consider disconnecting vNICs before removing VM and saving config + $VM | Get-VMNetworkAdapter | Disconnect-VMNetworkAdapter #Backup config + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Backing up VM config" Invoke-Command -ComputerName $SourceClusterName -ScriptBlock {Copy-Item -Path "$($using:VM.Path)\Virtual Machines" -Destination "$($using:VM.Path)\Virtual Machines Bak" -Recurse} #If there is different switch name in destination node, you should consider disconnecting vNICs first #$VM | Get-VMNetworkAdapter | Disconnect-VMNetworkAdapter - #Remove VM - $VM | Remove-VM -Force + #Remove VM (Just making sure hyper-v command is used, because SCVMM Remove-VM also removes VHDs) + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Removing VM" + $VM | Hyper-V\Remove-VM -Force #Restore Config #Invoke-Command -ComputerName $SourceClusterName -ScriptBlock {Copy-Item -Path "$($using:VM.Path)\Virtual Machines Bak\*" -Destination "$($using:VM.Path)\Virtual Machines" -Recurse} Invoke-Command -ComputerName $SourceClusterName -ScriptBlock {Move-Item -Path "$($using:VM.Path)\Virtual Machines Bak\*" -Destination "$($using:VM.Path)\Virtual Machines"} Invoke-Command -ComputerName $SourceClusterName -ScriptBlock {Remove-Item -Path "$($using:VM.Path)\Virtual Machines Bak\"} + #zip config to have a backup (in case something goes wrong with updating VM version) + Invoke-Command -ComputerName $SourceClusterName -ScriptBlock {Compress-Archive -Path "$($using:VM.Path)\Virtual Machines\" -DestinationPath "$($using:VM.Path)\Virtual Machines.zip"} #Copy machine to destination node using CredSSP $VolumeName=$DestinationStoragePath | Split-Path -Leaf + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Copying VM $($VM.VMName) to \\$DestinationClusterName\ClusterStorage$\$VolumeName\" Invoke-Command -ComputerName ($Servers | Get-Random) -Credential $Credentials -Authentication Credssp -ScriptBlock {Copy-Item -Path "$($using:VM.Path)" -Destination "\\$using:DestinationClusterName\ClusterStorage$\$using:VolumeName\" -Recurse} + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Copying VM $($VM.VMName) Finished" #Import VM and Start + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Importing VM $($VM.VMName) And Starting" $DestinationHost=(get-clusternode -cluster $DestinationClusterName | get-random).Name $NewVM=Import-VM -Path "$DestinationStoragePath\$($VM.Name)\Virtual Machines\$($VM.ID.GUID).vmcx" -CimSession $DestinationHost + $NewVM | Update-VMVersion -Force + $NewVM | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName $DestinationSwitchName $NewVM | Start-VM } @@ -213,6 +229,7 @@ $DestinationClusterName="AzSHCI-Cluster" $SourceClusterVolumes=(Get-ClusterSharedVolume -Cluster $SourceClusterName).sharedvolumeinfo.Friendlyvolumename $DestinationClusterVolumes=(Get-ClusterSharedVolume -Cluster $DestinationClusterName).sharedvolumeinfo.Friendlyvolumename + $DestinationSwitchName=(Get-VMSwitch -CimSession ((Get-ClusterNode -Cluster $ClusterName).Name | Select-Object -First 1)).Name # Temporarily enable CredSSP delegation to avoid double-hop issue $Servers=(get-clusternode -cluster $SourceClusterName).Name @@ -234,27 +251,45 @@ } $VMNames=(Get-VM -cimsession (get-clusternode -cluster $SourceClusterName).Name | Where-Object Path -Like "$SourceClusterVolume*").Name foreach ($VMName in $VMNames){ + + #remove VM from HA Resources + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Removing VM $($VM.VMName) From Cluster resources" Get-ClusterResource -Cluster $SourceClusterName -name "Virtual Machine $VMName" -ErrorAction Ignore | Remove-ClusterResource -force Get-ClusterGroup -Cluster $SourceClusterName -Name $VMName -ErrorAction Ignore | Remove-ClusterGroup -force + #Grab random node in cluster $DestinationClusterName (does not have to be random of course) $VM=Get-VM -Cimsession (get-clusternode -cluster $SourceClusterName).Name -Name $VMName - $VM | Stop-VM -Save - #If there is different switch name in destination node, you should consider disconnecting vNICs first - #$VM | Get-VMNetworkAdapter | Disconnect-VMNetworkAdapter + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Stopping VM $($VM.VMName)" + $VM | Stop-VM + #or just save it, but we need to update version anyway + #$VM | Stop-VM -Save + #If there is different switch name in destination node, you should consider disconnecting vNICs before removing VM and saving config + $VM | Get-VMNetworkAdapter | Disconnect-VMNetworkAdapter #Backup config + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Backing up VM config" Invoke-Command -ComputerName $SourceClusterName -ScriptBlock {Copy-Item -Path "$($using:VM.Path)\Virtual Machines" -Destination "$($using:VM.Path)\Virtual Machines Bak" -Recurse} - #Remove VM - $VM | Remove-VM -Force + #If there is different switch name in destination node, you should consider disconnecting vNICs first + #$VM | Get-VMNetworkAdapter | Disconnect-VMNetworkAdapter + #Remove VM (Just making sure hyper-v command is used, because SCVMM Remove-VM also removes VHDs) + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Removing VM" + $VM | Hyper-V\Remove-VM -Force #Restore Config #Invoke-Command -ComputerName $SourceClusterName -ScriptBlock {Copy-Item -Path "$($using:VM.Path)\Virtual Machines Bak\*" -Destination "$($using:VM.Path)\Virtual Machines" -Recurse} Invoke-Command -ComputerName $SourceClusterName -ScriptBlock {Move-Item -Path "$($using:VM.Path)\Virtual Machines Bak\*" -Destination "$($using:VM.Path)\Virtual Machines"} Invoke-Command -ComputerName $SourceClusterName -ScriptBlock {Remove-Item -Path "$($using:VM.Path)\Virtual Machines Bak\"} + #zip config to have a backup (in case something goes wrong with updating VM version) + Invoke-Command -ComputerName $SourceClusterName -ScriptBlock {Compress-Archive -Path "$($using:VM.Path)\Virtual Machines\" -DestinationPath "$($using:VM.Path)\Virtual Machines.zip"} #Copy machine to destination node using CredSSP - $VolumeName=$DestinationClusterVolumes[$index] | Split-Path -Leaf + $VolumeName=$DestinationStoragePath | Split-Path -Leaf + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Copying VM $($VM.VMName) to \\$DestinationClusterName\ClusterStorage$\$VolumeName\" Invoke-Command -ComputerName ($Servers | Get-Random) -Credential $Credentials -Authentication Credssp -ScriptBlock {Copy-Item -Path "$($using:VM.Path)" -Destination "\\$using:DestinationClusterName\ClusterStorage$\$using:VolumeName\" -Recurse} + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Copying VM $($VM.VMName) Finished" #Import VM and Start + Write-Output "$(get-date -Format 'yyyy/MM/dd hh:mm:ss tt') : Importing VM $($VM.VMName) And Starting" $DestinationHost=(get-clusternode -cluster $DestinationClusterName | get-random).Name - $NewVM=Import-VM -Path "$($DestinationClusterVolumes[$index])\$($VM.Name)\Virtual Machines\$($VM.ID.GUID).vmcx" -CimSession $DestinationHost + $NewVM=Import-VM -Path "$DestinationStoragePath\$($VM.Name)\Virtual Machines\$($VM.ID.GUID).vmcx" -CimSession $DestinationHost + $NewVM | Update-VMVersion -Force + $NewVM | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName $DestinationSwitchName $NewVM | Start-VM } }