From 74bf8c5333813fedfd6edfc498e601fde5f2e485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr?= Date: Fri, 16 Jun 2023 15:13:32 +0200 Subject: [PATCH 1/4] Codesigning support for scripts in Tools (#556) * Codesigning tools scripts during release. --- .github/workflows/create-prerelase.yml | 23 ++++--- .github/workflows/create-release.yml | 94 +++++++++++++------------- Scripts/1_Prereq.ps1 | 73 +++++++++++++------- Tools/CreateParentDisk.ps1 | 13 ++-- build.ps1 | 68 ++++++++++++++----- 5 files changed, 171 insertions(+), 100 deletions(-) diff --git a/.github/workflows/create-prerelase.yml b/.github/workflows/create-prerelase.yml index 2f1d668b..ce37d26c 100644 --- a/.github/workflows/create-prerelase.yml +++ b/.github/workflows/create-prerelase.yml @@ -8,6 +8,7 @@ on: push: paths: - 'Scripts/**' + - 'Tools/**' branches: [ dev ] jobs: @@ -16,22 +17,28 @@ jobs: if: "!contains(github.event.head_commit.message, '[no release]')" runs-on: windows-2019 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 1 - name: Build scripts id: build shell: powershell run: | - ./build.ps1 -Version prerelease + ./build.ps1 -Version dev $filename = "mslab_dev-$((Get-Date -Format "yyyyMMdd")).zip" mv ./Release.zip $filename - echo "::set-output name=filename::$filename" - - uses: "marvinpinto/action-automatic-releases@latest" + echo "filename=$filename" >> $env:GITHUB_OUTPUT + - name: Delete current dev prerelease + uses: cb80/delrel@latest with: - repo_token: "${{ secrets.GITHUB_TOKEN }}" - automatic_release_tag: "dev" + tag: dev + - name: Create new dev prerelease + uses: softprops/action-gh-release@v1 + with: + tag_name: dev + name: dev branch preview + generate_release_notes: true prerelease: true - title: "dev branch preview" files: | ${{ steps.build.outputs.filename }} + Output/Tools/*.ps1 diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index ea976540..456e9e8f 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -8,10 +8,12 @@ on: push: paths: - 'Scripts/**' + - 'Tools/**' branches: [ master ] jobs: new-version: + environment: release name: Bump version if: "!contains(github.event.head_commit.message, '[no release]')" runs-on: windows-2019 @@ -19,14 +21,15 @@ jobs: previous_tag: ${{ steps.bump.outputs.previous_tag }} new_tag: ${{ steps.bump.outputs.new_tag }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - id: bump name: Bump version run: | $today = Get-Date $newVersion = @($today.ToString("yy"), $today.ToString("MM"), "1") git fetch --tags - $hash = git rev-list --tags --topo-order --max-count=1 + # Get the latest tag that matches our versioning schema (starts with letter v) + $hash = git rev-list --tags=v* --topo-order --max-count=1 if($hash) { $currentTag = git describe --tags $hash $parts = $currentTag.Substring(1) -split '\.' @@ -35,72 +38,67 @@ jobs: $newTag = "v" + ($newVersion -join ".") git tag $newTag + if(-not $?) { + throw "Tagging of new release version failed!" + } + + git push origin $newTag + "New version: $newTag" - echo "::set-output name=previous_tag::$currentTag" - echo "::set-output name=new_tag::$newTag" - - - name: Push version tag - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - tags: true + echo "previous_tag=$currentTag" >> $env:GITHUB_OUTPUT + echo "new_tag=$newTag" >> $env:GITHUB_OUTPUT new-release: name: Create release if: "!contains(github.event.head_commit.message, '[no release]')" - runs-on: windows-2019 + runs-on: self-hosted needs: new-version steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - enable-AzPSSession: true - - name: "Build scripts" - uses: azure/powershell@v1 + - name: Build and sign release scripts + shell: pwsh env: SIGN_SCRIPT_URI: ${{ secrets.SIGN_SCRIPT_URI }} - CLIENT_ID: ${{ secrets.CLIENT_ID }} # just to ofusctate it in the output - with: - azPSVersion: "latest" - inlineScript: | - ./build.ps1 -Version ${{ needs.new-version.outputs.new_tag }} -SignScripts $true -SignScriptUri $env:SIGN_SCRIPT_URI -ClientId $env:CLIENT_ID + CLIENT_ID: ${{ secrets.CLIENT_ID }} # just to obfusctate it in the output + run: | + ./build.ps1 -Version ${{ needs.new-version.outputs.new_tag }} -SignScripts $true -SignScriptUri $env:SIGN_SCRIPT_URI -ClientId $env:CLIENT_ID + Move-Item ./Release.zip mslab_${{ needs.new-version.outputs.new_tag }}.zip - name: Create changelog id: changelog shell: powershell run: | if("${{ needs.new-version.outputs.previous_tag }}" -ne "") { - $changelog = (& { git log ${{ needs.new-version.outputs.previous_tag }}..HEAD --pretty=format:'- %s (%h)' --abbrev-commit -- Scripts }) -join '%0D%0A' + $changelog = (& { git log ${{ needs.new-version.outputs.previous_tag }}..HEAD --pretty=format:'- %s (%h)' --abbrev-commit -- Scripts Tools }) -join "`n" "Changes for ${{ needs.new-version.outputs.previous_tag }} are:" $changelog } else { $changelog = "" } - echo "::set-output name=changelog::$changelog" - - - name: Create Github Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + $changeLogContent = @" + :package: All MSLab scripts are in **mslab_${{ needs.new-version.outputs.new_tag }}.zip** file. + + :information_source: Remaining `.ps1` files in this release would be downloaded on-demand by MSLab scripts during deployment, only if needed. + "@ + + if($changelog -ne "") { + $changeLogContent += @" + + :basket: Changes in this version: + $changelog + "@ + } + + Set-Content -Value $changeLogContent -Path .\changelog.md + - name: Create new release + uses: softprops/action-gh-release@v1 with: tag_name: ${{ needs.new-version.outputs.new_tag }} # ${{ github.ref }} - release_name: Release ${{ needs.new-version.outputs.new_tag }} # ${{ github.ref }} - body: | - Changes in this version: - ${{ steps.changelog.outputs.changelog }} - draft: false - prerelease: false - - - name: Upload ZIP to Release - id: upload-scripts - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./Release.zip - asset_name: mslab_${{ needs.new-version.outputs.new_tag }}.zip - asset_content_type: application/zip + name: Release ${{ needs.new-version.outputs.new_tag }} # ${{ github.ref }} + generate_release_notes: true + body_path: changelog.md + files: | + mslab_${{ needs.new-version.outputs.new_tag }}.zip + Output/Tools/*.ps1 diff --git a/Scripts/1_Prereq.ps1 b/Scripts/1_Prereq.ps1 index b4fc34ff..234c40e2 100644 --- a/Scripts/1_Prereq.ps1 +++ b/Scripts/1_Prereq.ps1 @@ -83,37 +83,58 @@ function Get-WindowsBuildNumber { #region Download Scripts #add scripts for VMM - $Filenames="1_SQL_Install","2_ADK_Install","3_SCVMM_Install" - foreach ($Filename in $filenames){ - $Path="$PSScriptRoot\Temp\ToolsVHD\SCVMM\$Filename.ps1" - If (Test-Path -Path $Path){ + $filenames = "1_SQL_Install", "2_ADK_Install", "3_SCVMM_Install" + foreach ($filename in $filenames) { + $Path = "$PSScriptRoot\Temp\ToolsVHD\SCVMM\$filename.ps1" + if (Test-Path -Path $Path) { WriteSuccess "`t $Filename is present, skipping download" - }else{ - $FileContent=$null - $FileContent = (Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/Microsoft/MSLab/master/Tools/$Filename.ps1").Content - if ($FileContent){ + } else { + $FileContent = $null + + try { + # try to download tagged version first + $FileContent = (Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/microsoft/MSLab/$mslabVersion/Tools/$filename.ps1").Content + } catch { + WriteInfo "Download $filename failed with $($_.Exception.Message), trying master branch now" + # if that fails, try master branch + $FileContent = (Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/microsoft/MSLab/master/Tools/$filename.ps1").Content + } + + if ($FileContent) { $script = New-Item $Path -type File -Force $FileContent=$FileContent -replace "PasswordGoesHere",$LabConfig.AdminPassword #only applies to 1_SQL_Install and 3_SCVMM_Install.ps1 $FileContent=$FileContent -replace "DomainNameGoesHere",$LabConfig.DomainNetbiosName #only applies to 1_SQL_Install and 3_SCVMM_Install.ps1 Set-Content -path $script -value $FileContent - }else{ + } else { WriteErrorAndExit "Unable to download $Filename." } } } # add createparentdisks, DownloadLatestCU and PatchParentDisks scripts to Parent Disks folder - $FileNames = "CreateParentDisk", "DownloadLatestCUs", "PatchParentDisks", "CreateVMFleetDisk" + $fileNames = "CreateParentDisk", "DownloadLatestCUs", "PatchParentDisks", "CreateVMFleetDisk" if($LabConfig.Linux) { - $FileNames += "CreateLinuxParentDisk" + $fileNames += "CreateLinuxParentDisk" } - foreach ($filename in $filenames) { + foreach ($filename in $fileNames) { $Path="$PSScriptRoot\ParentDisks\$FileName.ps1" If (Test-Path -Path $Path) { WriteSuccess "`t $Filename is present, skipping download" } else { $FileContent = $null - $FileContent = (Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/Microsoft/MSLab/master/Tools/$FileName.ps1").Content + + try { + # try to download release version first + $file = (Invoke-WebRequest -UseBasicParsing -Uri "https://github.com/microsoft/MSLab/releases/download/$mslabVersion/$Filename.ps1") + if($file.Headers["Content-Type"] -eq "application/octet-stream") { + $FileContent = [System.Text.Encoding]::UTF8.GetString($file.Content) + } + } catch { + WriteInfo "Download $filename failed with $($_.Exception.Message), trying master branch now" + # if that fails, try main branch + $FileContent = (Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/microsoft/MSLab/master/Tools/$FileName.ps1").Content + } + if ($FileContent) { $script = New-Item "$PSScriptRoot\ParentDisks\$FileName.ps1" -type File -Force Set-Content -path $script -value $FileContent @@ -124,17 +145,23 @@ function Get-WindowsBuildNumber { } # Download convert-windowsimage into Temp -WriteInfoHighlighted "Testing Convert-windowsimage presence" -If ( Test-Path -Path "$PSScriptRoot\Temp\Convert-WindowsImage.ps1" ) { - WriteSuccess "`t Convert-windowsimage.ps1 is present, skipping download" -}else{ - WriteInfo "`t Downloading Convert-WindowsImage" - try { - Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/microsoft/MSLab/master/Tools/Convert-WindowsImage.ps1" -OutFile "$PSScriptRoot\Temp\Convert-WindowsImage.ps1" - } catch { - WriteError "`t Failed to download Convert-WindowsImage.ps1!" + WriteInfoHighlighted "Testing Convert-windowsimage presence" + $convertWindowsImagePath = "$PSScriptRoot\Temp\Convert-WindowsImage.ps1" + If (Test-Path -Path $convertWindowsImagePath) { + WriteSuccess "`t Convert-windowsimage.ps1 is present, skipping download" + } else { + WriteInfo "`t Downloading Convert-WindowsImage" + try { + Invoke-WebRequest -UseBasicParsing -Uri "https://github.com/microsoft/MSLab/releases/download/$mslabVersion/Convert-WindowsImage.ps1" -OutFile $convertWindowsImagePath + } catch { + try { + WriteInfo "Download Convert-windowsimage.ps1 failed with $($_.Exception.Message), trying master branch now" + Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/microsoft/MSLab/master/Tools/Convert-WindowsImage.ps1" -OutFile $convertWindowsImagePath + } catch { + WriteError "`t Failed to download Convert-WindowsImage.ps1!" + } + } } -} #endregion #region some tools to download diff --git a/Tools/CreateParentDisk.ps1 b/Tools/CreateParentDisk.ps1 index f4931583..95ccda3d 100644 --- a/Tools/CreateParentDisk.ps1 +++ b/Tools/CreateParentDisk.ps1 @@ -40,13 +40,18 @@ If (-not $isAdmin) { #endregion #region download convert-windowsimage if needed and load it - - if (!(Test-Path "$PSScriptRoot\Convert-WindowsImage.ps1")){ + $convertWindowsImagePath = "$PSScriptRoot\Convert-WindowsImage.ps1" + if (-not (Test-Path -Path $convertWindowsImagePath)) { WriteInfo "`t Downloading Convert-WindowsImage" try { - Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/microsoft/MSLab/master/Tools/Convert-WindowsImage.ps1" -OutFile "$PSScriptRoot\Convert-WindowsImage.ps1" + Invoke-WebRequest -UseBasicParsing -Uri "https://github.com/microsoft/MSLab/releases/download/$mslabVersion/Convert-WindowsImage.ps1" -OutFile $convertWindowsImagePath } catch { - WriteErrorAndExit "`t Failed to download Convert-WindowsImage.ps1!" + try { + WriteInfo "Download Convert-windowsimage.ps1 from releases ($mslabVersion) failed with $($_.Exception.Message), trying master branch now" + Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/microsoft/MSLab/master/Tools/Convert-WindowsImage.ps1" -OutFile $convertWindowsImagePath + } catch { + WriteError "`t Failed to download Convert-WindowsImage.ps1!" + } } } diff --git a/build.ps1 b/build.ps1 index f05f5f17..602298d6 100644 --- a/build.ps1 +++ b/build.ps1 @@ -11,12 +11,19 @@ param( [string]$ClientId ) +$toolsDir = ".\Tools\" $baseDir = ".\Scripts\" -$outputDir = ".\Output" +$outputBaseDir = ".\Output\" +$outputDir = "$($outputBaseDir)\Compiled" +$signedOutputDir = "$($outputBaseDir)\Signed" +$toolsOutputDir = "$($outputBaseDir)\Tools" $outputFile = "Release.zip" + [array]$ignoredFiles = "0_Shared.ps1" [array]$ignoredFilesToSign = @() #"LabConfig.ps1" +[array]$toolsIgnoredFilesToSign = @("1_SQL_Install.ps1", "2_ADK_Install.ps1", "3_SCVMM_Install.ps1") +#region Build (and optionally sign) Scripts if(Test-Path -Path $outputDir) { Remove-Item -Path $outputDir -Recurse -Force } @@ -63,31 +70,58 @@ foreach($file in $files) { $outputFullPath = $releaseDirectory.FullName if($SignScripts) { - # Download signing script - Invoke-WebRequest -Uri $SignScriptUri -OutFile .\sign.ps1 - + # Download signing script with Managed Identity + $token = Invoke-RestMethod -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fstorage.azure.com%2F" -Headers @{ "Metadata" = "true" } + Invoke-WebRequest -Headers @{ "x-ms-version" = "2017-11-09"; "Authorization" = "Bearer $($token.access_token)" } -Uri $SignScriptUri -OutFile .\sign.ps1 + . .\sign.ps1 +} - $signedOutputDir = "$($outputDir)\Signed" - if(Test-Path -Path $signedOutputDir) { - Remove-Item -Path $signedOutputDir -Recurse -Force - } +if(Test-Path -Path $signedOutputDir) { + Remove-Item -Path $signedOutputDir -Recurse -Force +} - $signedReleaseDirectory = New-Item -ItemType "Directory" -Path ".\" -Name $signedOutputDir - $files = Get-ChildItem -Path $releaseDirectory -File | Where-Object Name -NotIn $ignoredFilesToSign +$signedReleaseDirectory = New-Item -ItemType "Directory" -Path ".\" -Name $signedOutputDir +$files = Get-ChildItem -Path $releaseDirectory -File | Where-Object Name -NotIn $ignoredFilesToSign +if($SignScripts) { # sign scripts Invoke-CodeSign -Files $files -OutputPath $signedReleaseDirectory -ClientId $ClientId +} else { + # if not signing, just copy files over as is + $files | Select-Object -ExpandProperty FullName | Copy-Item -Destination $signedReleaseDirectory +} - $signedFiles = Get-ChildItem -Path $signedReleaseDirectory.FullName - if($files.Length -ne $signedFiles.Length) { - throw "Signing files failed (source count: $($files.Length), signedCount: $($signedFiles.Length))" - } +$signedFiles = Get-ChildItem -Path $signedReleaseDirectory.FullName +if($files.Length -ne $signedFiles.Length) { + throw "Signing files failed (source count: $($files.Length), signedCount: $($signedFiles.Length))" +} +#endregion + +#region Build (and optionally sign) Tools +if(Test-Path -Path $ToolsOutputDir) { + Remove-Item -Path $ToolsOutputDir -Recurse -Force +} +# and copy scripts that are ignored from signing +Get-ChildItem -Path $releaseDirectory -File | Where-Object Name -In $ignoredFilesToSign | Copy-Item -Destination $signedReleaseDirectory.FullName + +$outputFullPath = $signedReleaseDirectory.FullName - # and copy scripts that are ignored from signing - Get-ChildItem -Path $releaseDirectory -File | Where-Object Name -In $ignoredFilesToSign | Copy-Item -Destination $signedReleaseDirectory.FullName +$toolsSignedDirectory = New-Item -ItemType "Directory" -Path ".\" -Name $toolsOutputDir +$toolsFiles = Get-ChildItem -Path $toolsDir -File | Where-Object Name -NotIn $toolsIgnoredFilesToSign + +if($SignScripts) { + # Sign scripts in Tools folder + Invoke-CodeSign -Files $toolsFiles -OutputPath $toolsSignedDirectory -ClientId $ClientId +} else { + # or just copy tools scripts over + $toolsFiles | Select-Object -ExpandProperty FullName | Copy-Item -Destination $toolsSignedDirectory +} - $outputFullPath = $signedReleaseDirectory.FullName +$signedToolsFiles = Get-ChildItem -Path $toolsSignedDirectory.FullName +if($toolsFiles.Length -ne $signedToolsFiles.Length) { + throw "Signing files failed (source count: $($toolsFiles.Length), signedCount: $($signedToolsFiles.Length))" } +#endregion Compress-Archive -Path "$($outputFullPath)\*" -DestinationPath $outputFile -CompressionLevel Optimal -Force From 18286d66d81babc8fcf55cfa4faaf667bc730b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr?= Date: Fri, 16 Jun 2023 20:46:42 +0200 Subject: [PATCH 2/4] Updated build script and pipeline. (#557) --- .github/workflows/create-release.yml | 10 +- Tools/CreateParentDisk.ps1 | 2 + build.ps1 | 136 +++++++++++++++++---------- 3 files changed, 94 insertions(+), 54 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 456e9e8f..63432c70 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -78,15 +78,15 @@ jobs: } $changeLogContent = @" - :package: All MSLab scripts are in **mslab_${{ needs.new-version.outputs.new_tag }}.zip** file. + :package: MSLab scripts are in **[mslab_${{ needs.new-version.outputs.new_tag }}.zip](${{ github.server_url }}/${{ github.repository }}/releases/download/${{ needs.new-version.outputs.new_tag }}/mslab_${{ needs.new-version.outputs.new_tag }}.zip)** file. :information_source: Remaining `.ps1` files in this release would be downloaded on-demand by MSLab scripts during deployment, only if needed. + "@ if($changelog -ne "") { $changeLogContent += @" - - :basket: Changes in this version: + ## Changes in this version $changelog "@ } @@ -96,8 +96,8 @@ jobs: uses: softprops/action-gh-release@v1 with: tag_name: ${{ needs.new-version.outputs.new_tag }} # ${{ github.ref }} - name: Release ${{ needs.new-version.outputs.new_tag }} # ${{ github.ref }} - generate_release_notes: true + name: MSLab ${{ needs.new-version.outputs.new_tag }} # ${{ github.ref }} + generate_release_notes: false body_path: changelog.md files: | mslab_${{ needs.new-version.outputs.new_tag }}.zip diff --git a/Tools/CreateParentDisk.ps1 b/Tools/CreateParentDisk.ps1 index 95ccda3d..4c75eccb 100644 --- a/Tools/CreateParentDisk.ps1 +++ b/Tools/CreateParentDisk.ps1 @@ -39,6 +39,8 @@ If (-not $isAdmin) { #endregion + $mslabVersion = "dev" + #region download convert-windowsimage if needed and load it $convertWindowsImagePath = "$PSScriptRoot\Convert-WindowsImage.ps1" if (-not (Test-Path -Path $convertWindowsImagePath)) { diff --git a/build.ps1 b/build.ps1 index 602298d6..73be65f8 100644 --- a/build.ps1 +++ b/build.ps1 @@ -5,36 +5,53 @@ param( [string]$Version, [Parameter(Mandatory = $false, ParameterSetName = 'BuildAndSign')] [bool]$SignScripts = $false, - [Parameter(Mandatory = $true, ParameterSetName = 'BuildAndSign')] - [string]$SignScriptUri, - [Parameter(Mandatory = $true, ParameterSetName = 'BuildAndSign')] - [string]$ClientId + [Parameter(Mandatory = $true, ParameterSetName = 'BuildAndSign', HelpMessage = "Azure Blob URI to code signing script")] + [string]$SignScriptUri = "", + [Parameter(Mandatory = $true, ParameterSetName = 'BuildAndSign', HelpMessage = "Client ID of Code Signing App Registration")] + [string]$ClientId = "" ) +#region Configuration $toolsDir = ".\Tools\" -$baseDir = ".\Scripts\" +$scriptsDir = ".\Scripts\" $outputBaseDir = ".\Output\" -$outputDir = "$($outputBaseDir)\Compiled" -$signedOutputDir = "$($outputBaseDir)\Signed" -$toolsOutputDir = "$($outputBaseDir)\Tools" -$outputFile = "Release.zip" - -[array]$ignoredFiles = "0_Shared.ps1" -[array]$ignoredFilesToSign = @() #"LabConfig.ps1" +$scriptsOutputDir = Join-Path $outputBaseDir "ScriptsCompiled" +$signedScriptsOutputDir = Join-Path $outputBaseDir "Scripts" +$toolsOutputDir = Join-Path $outputBaseDir "ToolsCompiled" +$signedToolsOutputDir = Join-Path $outputBaseDir "Tools" +$scriptsOutputFile = "Release.zip" + +# Files that would be skipped by Build function (no replacements) +[array]$scriptsBuildIgnoredFiles = "0_Shared.ps1" +[array]$toolsBuildIgnoredFiles = @() + +# Files that won't be signed after build function +[array]$scriptsIgnoredFilesToSign = @() #"LabConfig.ps1" [array]$toolsIgnoredFilesToSign = @("1_SQL_Install.ps1", "2_ADK_Install.ps1", "3_SCVMM_Install.ps1") +#endregion -#region Build (and optionally sign) Scripts -if(Test-Path -Path $outputDir) { - Remove-Item -Path $outputDir -Recurse -Force +#region Init +if($SignScripts) { + # Download signing script with Managed Identity + $response = Invoke-RestMethod -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fstorage.azure.com%2F" -Headers @{ "Metadata" = "true" } + Invoke-WebRequest -Headers @{ "x-ms-version" = "2017-11-09"; "Authorization" = "Bearer $($response.access_token)" } -Uri $SignScriptUri -OutFile .\sign.ps1 + + . .\sign.ps1 } -$releaseDirectory = New-Item -ItemType "Directory" -Path ".\" -Name $outputDir -$files = Get-ChildItem -Path $baseDir -foreach($file in $files) { - if($file.Name -in $ignoredFiles) { - continue - } - $content = Get-Content -Path $file.FullName +if(Test-Path -Path $outputBaseDir) { + Remove-Item -Path $outputBaseDir -Recurse -Force +} +#endregion + +#region Functions +function Build-File { + param ( + [string]$InputFilePath, + [string]$OutputFilePath + ) + + $content = Get-Content -Path $InputFilePath $output = $content | ForEach-Object { $line = $_ @@ -49,7 +66,7 @@ foreach($file in $files) { if($includeFile.StartsWith(".\")) { $includeFile = $includeFile.Substring(2) } - $includeFile = Join-Path -Path $baseDir -ChildPath $includeFile + $includeFile = Join-Path -Path $scriptsDir -ChildPath $includeFile if(-not (Test-Path -Path $includeFile)) { throw "Unable to include requested script ($includeFile)" } @@ -63,52 +80,70 @@ foreach($file in $files) { $line } - $outFile = Join-Path -Path $releaseDirectory -ChildPath $file.Name - Set-Content -Path $outFile -Value $output + + Set-Content -Path $OutputFilePath -Value $output } +#endregion -$outputFullPath = $releaseDirectory.FullName +#region Build (and optionally sign) Scripts +if(Test-Path -Path $scriptsOutputDir) { + Remove-Item -Path $scriptsOutputDir -Recurse -Force +} -if($SignScripts) { - # Download signing script with Managed Identity - $token = Invoke-RestMethod -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fstorage.azure.com%2F" -Headers @{ "Metadata" = "true" } - Invoke-WebRequest -Headers @{ "x-ms-version" = "2017-11-09"; "Authorization" = "Bearer $($token.access_token)" } -Uri $SignScriptUri -OutFile .\sign.ps1 - - . .\sign.ps1 +if(Test-Path -Path $signedScriptsOutputDir) { + Remove-Item -Path $signedScriptsOutputDir -Recurse -Force } -if(Test-Path -Path $signedOutputDir) { - Remove-Item -Path $signedOutputDir -Recurse -Force +$scriptsReleaseDirectory = New-Item -ItemType "Directory" -Path ".\" -Name $scriptsOutputDir +$scriptsFiles = Get-ChildItem -Path $scriptsDir +foreach($file in $scriptsFiles) { + if($file.Name -in $scriptsBuildIgnoredFiles) { + continue + } + + $outFile = Join-Path -Path $scriptsReleaseDirectory -ChildPath $file.Name + Build-File -InputFilePath $file.FullName -OutputFilePath $outFile } -$signedReleaseDirectory = New-Item -ItemType "Directory" -Path ".\" -Name $signedOutputDir -$files = Get-ChildItem -Path $releaseDirectory -File | Where-Object Name -NotIn $ignoredFilesToSign +$scriptsSignedDirectory = New-Item -ItemType "Directory" -Path ".\" -Name $signedScriptsOutputDir +$scriptFiles = Get-ChildItem -Path $scriptsReleaseDirectory -File | Where-Object Name -NotIn $scriptsIgnoredFilesToSign if($SignScripts) { # sign scripts - Invoke-CodeSign -Files $files -OutputPath $signedReleaseDirectory -ClientId $ClientId + Invoke-CodeSign -Files $scriptFiles -OutputPath $scriptsSignedDirectory -ClientId $ClientId } else { # if not signing, just copy files over as is - $files | Select-Object -ExpandProperty FullName | Copy-Item -Destination $signedReleaseDirectory + $scriptFiles | Select-Object -ExpandProperty FullName | Copy-Item -Destination $scriptsSignedDirectory } -$signedFiles = Get-ChildItem -Path $signedReleaseDirectory.FullName -if($files.Length -ne $signedFiles.Length) { - throw "Signing files failed (source count: $($files.Length), signedCount: $($signedFiles.Length))" +$signedScriptFiles = Get-ChildItem -Path $scriptsSignedDirectory.FullName +if($scriptFiles.Length -ne $signedScriptFiles.Length) { + throw "Signing files failed (source count: $($scriptFiles.Length), signedCount: $($signedScriptFiles.Length))" } #endregion #region Build (and optionally sign) Tools -if(Test-Path -Path $ToolsOutputDir) { - Remove-Item -Path $ToolsOutputDir -Recurse -Force +if(Test-Path -Path $toolsOutputDir) { + Remove-Item -Path $toolsOutputDir -Recurse -Force +} + +if(Test-Path -Path $signedToolsOutputDir) { + Remove-Item -Path $signedToolsOutputDir -Recurse -Force } -# and copy scripts that are ignored from signing -Get-ChildItem -Path $releaseDirectory -File | Where-Object Name -In $ignoredFilesToSign | Copy-Item -Destination $signedReleaseDirectory.FullName -$outputFullPath = $signedReleaseDirectory.FullName +$toolsReleaseDirectory = New-Item -ItemType "Directory" -Path ".\" -Name $toolsOutputDir +$toolsFiles = Get-ChildItem -Path $toolsDir +foreach($file in $toolsFiles) { + if($file.Name -in $toolsBuildIgnoredFiles) { + continue + } + + $outFile = Join-Path -Path $toolsReleaseDirectory -ChildPath $file.Name + Build-File -InputFilePath $file.FullName -OutputFilePath $outFile +} -$toolsSignedDirectory = New-Item -ItemType "Directory" -Path ".\" -Name $toolsOutputDir -$toolsFiles = Get-ChildItem -Path $toolsDir -File | Where-Object Name -NotIn $toolsIgnoredFilesToSign +$toolsSignedDirectory = New-Item -ItemType "Directory" -Path ".\" -Name $signedToolsOutputDir +$toolsFiles = Get-ChildItem -Path $toolsReleaseDirectory -File | Where-Object Name -NotIn $toolsIgnoredFilesToSign if($SignScripts) { # Sign scripts in Tools folder @@ -124,4 +159,7 @@ if($toolsFiles.Length -ne $signedToolsFiles.Length) { } #endregion -Compress-Archive -Path "$($outputFullPath)\*" -DestinationPath $outputFile -CompressionLevel Optimal -Force +#region Create Scripts release ZIP +$scriptsOutputFullPath = $scriptsSignedDirectory.FullName +Compress-Archive -Path "$($scriptsOutputFullPath)\*" -DestinationPath $scriptsOutputFile -CompressionLevel Optimal -Force +#endregion \ No newline at end of file From ffa32c2b52720ff6f5b1f2e39c8e76bd84409ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr?= Date: Fri, 16 Jun 2023 21:48:17 +0200 Subject: [PATCH 3/4] Fix for download of script to properly keep signature. (#558) --- Scripts/1_Prereq.ps1 | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/Scripts/1_Prereq.ps1 b/Scripts/1_Prereq.ps1 index 234c40e2..14c1a4c0 100644 --- a/Scripts/1_Prereq.ps1 +++ b/Scripts/1_Prereq.ps1 @@ -104,7 +104,7 @@ function Get-WindowsBuildNumber { $script = New-Item $Path -type File -Force $FileContent=$FileContent -replace "PasswordGoesHere",$LabConfig.AdminPassword #only applies to 1_SQL_Install and 3_SCVMM_Install.ps1 $FileContent=$FileContent -replace "DomainNameGoesHere",$LabConfig.DomainNetbiosName #only applies to 1_SQL_Install and 3_SCVMM_Install.ps1 - Set-Content -path $script -value $FileContent + Set-Content -Path $script -value $FileContent } else { WriteErrorAndExit "Unable to download $Filename." } @@ -117,29 +117,26 @@ function Get-WindowsBuildNumber { $fileNames += "CreateLinuxParentDisk" } foreach ($filename in $fileNames) { - $Path="$PSScriptRoot\ParentDisks\$FileName.ps1" - If (Test-Path -Path $Path) { - WriteSuccess "`t $Filename is present, skipping download" + $path = "$PSScriptRoot\ParentDisks\$FileName.ps1" + If (Test-Path -Path $path) { + WriteSuccess "`t $filename is present, skipping download" } else { $FileContent = $null try { # try to download release version first - $file = (Invoke-WebRequest -UseBasicParsing -Uri "https://github.com/microsoft/MSLab/releases/download/$mslabVersion/$Filename.ps1") - if($file.Headers["Content-Type"] -eq "application/octet-stream") { - $FileContent = [System.Text.Encoding]::UTF8.GetString($file.Content) - } + Invoke-WebRequest -UseBasicParsing -Uri "https://github.com/microsoft/MSLab/releases/download/$mslabVersion/$Filename.ps1" -OutFile $path } catch { WriteInfo "Download $filename failed with $($_.Exception.Message), trying master branch now" - # if that fails, try main branch + + # if that fails, try master branch $FileContent = (Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/microsoft/MSLab/master/Tools/$FileName.ps1").Content - } - - if ($FileContent) { - $script = New-Item "$PSScriptRoot\ParentDisks\$FileName.ps1" -type File -Force - Set-Content -path $script -value $FileContent - } else { - WriteErrorAndExit "Unable to download $Filename." + if ($FileContent) { + $script = New-Item $path -type File -Force + Set-Content -Path $script -value $FileContent + } else { + WriteErrorAndExit "Unable to download $Filename." + } } } } From e358d0c7ed41b16fb21a45b9afb551eca7de87ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Mach?= Date: Tue, 14 Nov 2023 20:25:44 +0100 Subject: [PATCH 4/4] Fix PSScriptAnalyzer warnings. --- Scripts/0_Shared.ps1 | 6 +- Scripts/1_Prereq.ps1 | 52 ++++----- Scripts/2_CreateParentDisks.ps1 | 182 ++++++++++++++++---------------- Scripts/3_Deploy.ps1 | 127 +++++++++++----------- Scripts/Cleanup.ps1 | 14 +-- Scripts/LabConfig.ps1 | 34 +++--- 6 files changed, 207 insertions(+), 208 deletions(-) diff --git a/Scripts/0_Shared.ps1 b/Scripts/0_Shared.ps1 index a7db48bf..3a4893db 100644 --- a/Scripts/0_Shared.ps1 +++ b/Scripts/0_Shared.ps1 @@ -155,7 +155,7 @@ function Get-PcSystemType { $aiPropertyCache = @{} -function New-TelemetryEvent { +function Initialize-TelemetryEvent { param( [Parameter(Mandatory = $true)] [string]$Event, @@ -303,12 +303,12 @@ function Send-TelemetryEvent { ) process { - $telemetryEvent = New-TelemetryEvent -Event $Event -Properties $Properties -Metrics $Metrics -NickName $NickName + $telemetryEvent = Initialize-TelemetryEvent -Event $Event -Properties $Properties -Metrics $Metrics -NickName $NickName Send-TelemetryObject -Data $telemetryEvent } } -function Send-TelemetryEvents { +function Send-TelemetryEvent { param( [Parameter(Mandatory = $true)] [array]$Events diff --git a/Scripts/1_Prereq.ps1 b/Scripts/1_Prereq.ps1 index e2df7c8a..d2276b69 100644 --- a/Scripts/1_Prereq.ps1 +++ b/Scripts/1_Prereq.ps1 @@ -4,11 +4,11 @@ If (-not $isAdmin) { Write-Host "-- Restarting as Administrator" -ForegroundColor Cyan ; Start-Sleep -Seconds 1 if($PSVersionTable.PSEdition -eq "Core") { - Start-Process pwsh.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs + Start-Process pwsh.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs } else { - Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs + Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs } - + exit } @@ -18,10 +18,10 @@ If (-not $isAdmin) { #region Functions . $PSScriptRoot\0_Shared.ps1 # [!build-include-inline] -function Get-WindowsBuildNumber { +function Get-WindowsBuildNumber { $os = Get-CimInstance -ClassName Win32_OperatingSystem - return [int]($os.BuildNumber) -} + return [int]($os.BuildNumber) +} #endregion #region Initialization @@ -128,7 +128,7 @@ function Get-WindowsBuildNumber { Invoke-WebRequest -UseBasicParsing -Uri "https://github.com/microsoft/MSLab/releases/download/$mslabVersion/$Filename.ps1" -OutFile $path } catch { WriteInfo "Download $filename failed with $($_.Exception.Message), trying master branch now" - + # if that fails, try master branch $FileContent = (Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/microsoft/MSLab/master/Tools/$FileName.ps1").Content if ($FileContent) { @@ -166,7 +166,7 @@ function Get-WindowsBuildNumber { WriteInfoHighlighted "Testing diskspd presence" If ( Test-Path -Path "$PSScriptRoot\Temp\ToolsVHD\DiskSpd\diskspd.exe" ) { WriteSuccess "`t Diskspd is present, skipping download" - }else{ + }else{ WriteInfo "`t Diskspd not there - Downloading diskspd" try { <# aka.ms/diskspd changed. Commented @@ -197,13 +197,13 @@ function Get-WindowsBuildNumber { $modules=("xActiveDirectory","3.0.0.0"),("xDHCpServer","2.0.0.0"),("xDNSServer","1.15.0.0"),("NetworkingDSC","7.4.0.0"),("xPSDesiredStateConfiguration","8.10.0.0") foreach ($module in $modules){ - WriteInfoHighlighted "Testing if modules are present" + WriteInfoHighlighted "Testing if modules are present" $modulename=$module[0] $moduleversion=$module[1] if (!(Test-Path "$PSScriptRoot\Temp\DSC\$modulename\$Moduleversion")){ WriteInfo "`t Module $module not found... Downloading" - #Install NuGET package provider - if ((Get-PackageProvider -Name NuGet) -eq $null){ + #Install NuGET package provider + if ((Get-PackageProvider -Name NuGet) -eq $null){ Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Confirm:$false -Force } Find-DscResource -moduleName $modulename -RequiredVersion $moduleversion | Save-Module -Path "$PSScriptRoot\Temp\DSC" @@ -238,24 +238,24 @@ if($LabConfig.Linux -eq $true) { WriteInfo "`t Test Packer availability" # Packer - if (Get-Command "packer.exe" -ErrorAction SilentlyContinue) - { + if (Get-Command "packer.exe" -ErrorAction SilentlyContinue) + { WriteSuccess "`t Packer is in PATH." } else { WriteInfo "`t`t Downloading latest Packer binary" WriteInfo "`t`t Creating packer directory" - $linuxToolsDirPath = "$PSScriptRoot\LAB\bin" + $linuxToolsDirPath = "$PSScriptRoot\LAB\bin" New-Item $linuxToolsDirPath -ItemType Directory -Force | Out-Null - + if(-not (Test-Path (Join-Path $linuxToolsDirPath "packer.exe"))) { $packerReleaseInfo = Invoke-RestMethod -Uri "https://checkpoint-api.hashicorp.com/v1/check/packer" - $downloadUrl = "https://releases.hashicorp.com/packer/$($packerReleaseInfo.current_version)/packer_$($packerReleaseInfo.current_version)_windows_amd64.zip" - Start-BitsTransfer -Source $downloadUrl -Destination (Join-Path $linuxToolsDirPath "packer.zip") + $downloadUrl = "https://releases.hashicorp.com/packer/$($packerReleaseInfo.current_version)/packer_$($packerReleaseInfo.current_version)_windows_amd64.zip" + Start-BitsTransfer -Source $downloadUrl -Destination (Join-Path $linuxToolsDirPath "packer.zip") Expand-Archive -Path (Join-Path $linuxToolsDirPath "packer.zip") -DestinationPath $linuxToolsDirPath -Force - Remove-Item -Path (Join-Path $linuxToolsDirPath "packer.zip") + Remove-Item -Path (Join-Path $linuxToolsDirPath "packer.zip") } - + WriteInfo "`t`t Creating Packer firewall rule" $id = $PSScriptRoot -replace '[^a-zA-Z0-9]' $fwRule = Get-NetFirewallRule -Name "mslab-packer-$id" -ErrorAction SilentlyContinue @@ -268,7 +268,7 @@ if($LabConfig.Linux -eq $true) { WriteInfo "`t`t Downloading Packer templates" $packerTemplatesDirectory = "$PSScriptRoot\ParentDisks\PackerTemplates\" if (-not (Test-Path $packerTemplatesDirectory)) { - New-Item -Type Directory -Path $packerTemplatesDirectory + New-Item -Type Directory -Path $packerTemplatesDirectory } $templatesBase = "https://github.com/microsoft/mslab-templates/releases/latest/download/" @@ -309,11 +309,11 @@ if($LabConfig.Linux -eq $true) { if($comparison) { WriteError "`t SSH Keypair $($LabConfig.SshKeyPath) does not match." } - } - else + } + else { WriteInfo "`t`t Generating new SSH key pair" - $sshKeyDir = "$PSScriptRoot\LAB\.ssh" + $sshKeyDir = "$PSScriptRoot\LAB\.ssh" $key = "$sshKeyDir\lab_rsa" New-Item -ItemType Directory $sshKeyDir -ErrorAction SilentlyContinue | Out-Null ssh-keygen.exe -t rsa -b 4096 -C "$($LabConfig.DomainAdminName)" -f $key -q -N '""' @@ -326,11 +326,11 @@ if((Get-TelemetryLevel) -in $TelemetryEnabledLevels) { $metrics = @{ 'script.duration' = ((Get-Date) - $StartDateTime).TotalSeconds } - + Send-TelemetryEvent -Event "Prereq.End" -Metrics $metrics -NickName $LabConfig.TelemetryNickName | Out-Null } -# finishing +# finishing WriteInfo "Script finished at $(Get-date) and took $(((get-date) - $StartDateTime).TotalMinutes) Minutes" Stop-Transcript @@ -338,4 +338,4 @@ Stop-Transcript If (!$LabConfig.AutoClosePSWindows) { WriteSuccess "Press enter to continue..." Read-Host | Out-Null -} \ No newline at end of file +} diff --git a/Scripts/2_CreateParentDisks.ps1 b/Scripts/2_CreateParentDisks.ps1 index fe4c1d05..d37352f0 100644 --- a/Scripts/2_CreateParentDisks.ps1 +++ b/Scripts/2_CreateParentDisks.ps1 @@ -4,9 +4,9 @@ If (-not $isAdmin) { Write-Host "-- Restarting as Administrator" -ForegroundColor Cyan ; Start-Sleep -Seconds 1 if($PSVersionTable.PSEdition -eq "Core") { - Start-Process pwsh.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs + Start-Process pwsh.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs } else { - Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs + Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs } exit @@ -15,7 +15,7 @@ If (-not $isAdmin) { #region Functions . $PSScriptRoot\0_Shared.ps1 # [!build-include-inline] - #Create Unattend for VHD + #Create Unattend for VHD Function CreateUnattendFileVHD{ param ( [parameter(Mandatory=$true)] @@ -72,8 +72,8 @@ If (-not $isAdmin) { true - true - true + true + true $TimeZone @@ -85,7 +85,7 @@ If (-not $isAdmin) { Set-Content -path $unattendFile -value $fileContent #return the file object - Return $unattendFile + Return $unattendFile } #endregion @@ -142,7 +142,7 @@ If (-not $isAdmin) { $LabConfig.DomainName.Split(".") | ForEach-Object { $DN+="DC=$_," } - + $LabConfig.DN=$DN.TrimEnd(",") $AdminPassword=$LabConfig.AdminPassword @@ -158,7 +158,7 @@ If (-not $isAdmin) { #DCHP scope $DHCPscope = $LabConfig.DHCPscope $ReverseDNSrecord = $DHCPscope -replace '^(\d+)\.(\d+)\.\d+\.(\d+)$','$3.$2.$1.in-addr.arpa' - $DHCPscope = $DHCPscope.Substring(0,$DHCPscope.Length-1) + $DHCPscope = $DHCPscope.Substring(0,$DHCPscope.Length-1) #endregion @@ -191,7 +191,7 @@ If (-not $isAdmin) { if(!(Test-Path -Path "$PSScriptRoot\$_")){ WriteErrorAndExit "file $_ needed for SCVMM install not found. Exitting" } - } + } } if ($LabConfig.InstallSCVMM -eq "Prereqs"){ @@ -199,16 +199,16 @@ If (-not $isAdmin) { if(!(Test-Path -Path "$PSScriptRoot\$_")){ WriteErrorAndExit "file $_ needed for SCVMM Prereqs install not found. Exitting" } - } + } } - + if ($LabConfig.InstallSCVMM -eq "SQL"){ "Temp\ToolsVHD\SCVMM\ADK\ADKsetup.exe","Temp\ToolsVHD\SCVMM\SQL\setup.exe" | ForEach-Object { if(!(Test-Path -Path "$PSScriptRoot\$_")){ WriteErrorAndExit "file $_ needed for SQL install not found. Exitting" } } - } + } if ($LabConfig.InstallSCVMM -eq "ADK"){ "Temp\ToolsVHD\SCVMM\ADK\ADKsetup.exe" | ForEach-Object { @@ -221,7 +221,7 @@ If (-not $isAdmin) { #check if parent images already exist (this is useful if you have parent disks from another lab and you want to rebuild for example scvmm) WriteInfoHighlighted "Testing if some parent disk already exists and can be used" - + #grab all files in parentdisks folder $ParentDisksNames=(Get-ChildItem -Path "$PSScriptRoot\ParentDisks" -ErrorAction SilentlyContinue).Name @@ -245,7 +245,7 @@ If (-not $isAdmin) { WriteInfoHighlighted "Checking if volume filesystem is NTFS or ReFS" $driveletter=$PSScriptRoot -split ":" | Select-Object -First 1 if ($PSScriptRoot -like "c:\ClusterStorage*"){ - WriteSuccess "`t Volume Cluster Shared Volume. Mountdir will be $env:Temp\MSLabMountdir" + WriteSuccess "`t Volume Cluster Shared Volume. Mountdir will be $env:Temp\MSLabMountdir" $mountdir="$env:Temp\MSLabMountdir" $VolumeFileSystem="CSVFS" }else{ @@ -279,10 +279,10 @@ If (-not $isAdmin) { $openFile = New-Object System.Windows.Forms.OpenFileDialog -Property @{ Title="Please select ISO image with Windows Server 2016, 2019, 2022 or Server Insider" } - $openFile.Filter = "iso files (*.iso)|*.iso|All files (*.*)|*.*" + $openFile.Filter = "iso files (*.iso)|*.iso|All files (*.*)|*.*" If($openFile.ShowDialog() -eq "OK"){ WriteInfo "File $($openfile.FileName) selected" - } + } if (!$openFile.FileName){ WriteErrorAndExit "Iso was not selected... Exitting" } @@ -332,7 +332,7 @@ If (-not $isAdmin) { Multiselect = $true; Title = "Please select Windows Server Updates (*.msu). Click Cancel if you don't want any." } - $msupackages.Filter = "msu files (*.msu)|*.msu|All files (*.*)|*.*" + $msupackages.Filter = "msu files (*.msu)|*.msu|All files (*.*)|*.*" If($msupackages.ShowDialog() -eq "OK"){ WriteInfoHighlighted "Following patches selected:" WriteInfo "`t $($msupackages.filenames)" @@ -352,13 +352,13 @@ If (-not $isAdmin) { #Windows Server 2016 $ServerVHDs += @{ Kind = "Full" - Edition="4" + Edition="4" VHDName="Win2016_G2.vhdx" Size=127GB } $ServerVHDs += @{ Kind = "Core" - Edition="3" + Edition="3" VHDName="Win2016Core_G2.vhdx" Size=127GB } @@ -374,13 +374,13 @@ If (-not $isAdmin) { #Windows Server 2019 $ServerVHDs += @{ Kind = "Full" - Edition="4" + Edition="4" VHDName="Win2019_G2.vhdx" Size=127GB } $ServerVHDs += @{ Kind = "Core" - Edition="3" + Edition="3" VHDName="Win2019Core_G2.vhdx" Size=127GB } @@ -388,20 +388,20 @@ If (-not $isAdmin) { #Windows Server 2022 $ServerVHDs += @{ Kind = "Full" - Edition="4" + Edition="4" VHDName="Win2022_G2.vhdx" Size=127GB } $ServerVHDs += @{ Kind = "Core" - Edition="3" + Edition="3" VHDName="Win2022Core_G2.vhdx" Size=127GB } }elseif ($BuildNumber -gt 20348 -and $SAC){ $ServerVHDs += @{ Kind = "Core" - Edition="2" + Edition="2" VHDName="WinSrvInsiderCore_$BuildNumber.vhdx" Size=127GB } @@ -413,13 +413,13 @@ If (-not $isAdmin) { #Windows Sever Insider $ServerVHDs += @{ Kind = "Full" - Edition="4" + Edition="4" VHDName="WinSrvInsider_$BuildNumber.vhdx" Size=127GB } $ServerVHDs += @{ Kind = "Core" - Edition="3" + Edition="3" VHDName="WinSrvInsiderCore_$BuildNumber.vhdx" Size=127GB } @@ -450,7 +450,7 @@ If (-not $isAdmin) { 'ParentDisks','Temp','Temp\mountdir' | ForEach-Object { if (!( Test-Path "$PSScriptRoot\$_" )) { WriteInfoHighlighted "Creating Directory $_" - New-Item -Type Directory -Path "$PSScriptRoot\$_" + New-Item -Type Directory -Path "$PSScriptRoot\$_" } } @@ -528,8 +528,8 @@ If (-not $isAdmin) { $toolsVHD=New-VHD -Path "$PSScriptRoot\ParentDisks\tools.vhdx" -SizeBytes 300GB -Dynamic #mount and format VHD $VHDMount = Mount-VHD $toolsVHD.Path -Passthru - $vhddisk = $VHDMount| get-disk - $vhddiskpart = $vhddisk | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -UseMaximumSize -AssignDriveLetter |Format-Volume -FileSystem NTFS -AllocationUnitSize 8kb -NewFileSystemLabel ToolsDisk + $vhddisk = $VHDMount| get-disk + $vhddiskpart = $vhddisk | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -UseMaximumSize -AssignDriveLetter |Format-Volume -FileSystem NTFS -AllocationUnitSize 8kb -NewFileSystemLabel ToolsDisk $VHDPathTest=Test-Path -Path "$PSScriptRoot\Temp\ToolsVHD\" if (!$VHDPathTest){ @@ -539,7 +539,7 @@ If (-not $isAdmin) { WriteInfo "Found $PSScriptRoot\Temp\ToolsVHD\*, copying files into VHDX" Copy-Item -Path "$PSScriptRoot\Temp\ToolsVHD\*" -Destination "$($vhddiskpart.DriveLetter):\" -Recurse -Force }else{ - WriteInfo "Files not found" + WriteInfo "Files not found" WriteInfoHighlighted "Add required tools into $PSScriptRoot\Temp\ToolsVHD and Press any key to continue..." $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | OUT-NULL Copy-Item -Path "$PSScriptRoot\Temp\ToolsVHD\*" -Destination ($vhddiskpart.DriveLetter+':\') -Recurse -Force @@ -632,7 +632,7 @@ If (-not $isAdmin) { New-item -type directory -Path $mountdir -force [System.Version]$VMVersion=(Get-WindowsImage -ImagePath $VHDPath -Index 1).Version Mount-WindowsImage -Path $mountdir -ImagePath $VHDPath -Index 1 - Use-WindowsUnattend -Path $mountdir -UnattendPath $unattendFile + Use-WindowsUnattend -Path $mountdir -UnattendPath $unattendFile #&"$PSScriptRoot\Temp\dism\dism" /mount-image /imagefile:$vhdpath /index:1 /MountDir:$mountdir #&"$PSScriptRoot\Temp\dism\dism" /image:$mountdir /Apply-Unattend:$unattendfile New-item -type directory -Path "$mountdir\Windows\Panther" -force @@ -650,12 +650,12 @@ If (-not $isAdmin) { #Create DSC configuration configuration DCHydration { - param - ( - [Parameter(Mandatory)] - [pscredential]$safemodeAdministratorCred, - - [Parameter(Mandatory)] + param + ( + [Parameter(Mandatory)] + [pscredential]$safemodeAdministratorCred, + + [Parameter(Mandatory)] [pscredential]$domainCred, [Parameter(Mandatory)] @@ -670,12 +670,12 @@ If (-not $isAdmin) { Import-DSCResource -ModuleName xPSDesiredStateConfiguration -ModuleVersion "8.10.0.0" Import-DscResource -ModuleName PSDesiredStateConfiguration - Node $AllNodes.Where{$_.Role -eq "Parent DC"}.Nodename + Node $AllNodes.Where{$_.Role -eq "Parent DC"}.Nodename { - WindowsFeature ADDSInstall - { - Ensure = "Present" + WindowsFeature ADDSInstall + { + Ensure = "Present" Name = "AD-Domain-Services" } @@ -691,47 +691,47 @@ If (-not $isAdmin) { Ensure = "Present" Name = "RSAT-AD-PowerShell" DependsOn = "[WindowsFeature]ADDSInstall" - } + } WindowsFeature FeatureADAdminCenter { Ensure = "Present" Name = "RSAT-AD-AdminCenter" DependsOn = "[WindowsFeature]ADDSInstall" - } + } WindowsFeature FeatureADDSTools { Ensure = "Present" Name = "RSAT-ADDS-Tools" DependsOn = "[WindowsFeature]ADDSInstall" - } + } WindowsFeature FeatureDNSTools { Ensure = "Present" Name = "RSAT-DNS-Server" DependsOn = "[WindowsFeature]ADDSInstall" - } - - xADDomain FirstDS - { - DomainName = $Node.DomainName - DomainAdministratorCredential = $domainCred + } + + xADDomain FirstDS + { + DomainName = $Node.DomainName + DomainAdministratorCredential = $domainCred SafemodeAdministratorPassword = $safemodeAdministratorCred DomainNetbiosName = $node.DomainNetbiosName DependsOn = "[WindowsFeature]ADDSInstall" } - - xWaitForADDomain DscForestWait - { - DomainName = $Node.DomainName - DomainUserCredential = $domainCred - RetryCount = $Node.RetryCount - RetryIntervalSec = $Node.RetryIntervalSec - DependsOn = "[xADDomain]FirstDS" + + xWaitForADDomain DscForestWait + { + DomainName = $Node.DomainName + DomainUserCredential = $domainCred + RetryCount = $Node.RetryCount + RetryIntervalSec = $Node.RetryIntervalSec + DependsOn = "[xADDomain]FirstDS" } - + xADOrganizationalUnit DefaultOU { Name = $Node.DefaultOUName @@ -739,7 +739,7 @@ If (-not $isAdmin) { ProtectedFromAccidentalDeletion = $true Description = 'Default OU for all user and computer accounts' Ensure = 'Present' - DependsOn = "[xADDomain]FirstDS" + DependsOn = "[xADDomain]FirstDS" } xADUser SQL_SA @@ -851,7 +851,7 @@ If (-not $isAdmin) { Ensure = "Present" Name = "RSAT-DHCP" DependsOn = "[WindowsFeature]DHCPServer" - } + } xDhcpServerScope ManagementScope @@ -878,7 +878,7 @@ If (-not $isAdmin) { Router = ($DHCPscope+"1") DependsOn = "[Service]DHCPServer" } - + xDhcpServerAuthorization LocalServerActivation { Ensure = 'Present' @@ -913,7 +913,7 @@ If (-not $isAdmin) { State = "Started" DependsOn = "[WindowsFeature]DSCServiceFeature" } - + File RegistrationKeyFile { Ensure = 'Present' @@ -925,12 +925,12 @@ If (-not $isAdmin) { } } - $ConfigData = @{ - - AllNodes = @( - @{ - Nodename = $DCName - Role = "Parent DC" + $ConfigData = @{ + + AllNodes = @( + @{ + Nodename = $DCName + Role = "Parent DC" DomainAdminName=$LabConfig.DomainAdminName DomainName = $LabConfig.DomainName DomainNetbiosName = $LabConfig.DomainNetbiosName @@ -938,15 +938,15 @@ If (-not $isAdmin) { DefaultOUName=$LabConfig.DefaultOUName RegistrationKey='14fc8e72-5036-4e79-9f89-5382160053aa' PSDscAllowPlainTextPassword = $true - PsDscAllowDomainUser= $true - RetryCount = 50 - RetryIntervalSec = 30 - } - ) - } + PsDscAllowDomainUser= $true + RetryCount = 50 + RetryIntervalSec = 30 + } + ) + } #create LCM config - [DSCLocalConfigurationManager()] + [DSCLocalConfigurationManager()] configuration LCMConfig { Node DC @@ -954,7 +954,7 @@ If (-not $isAdmin) { Settings { RebootNodeIfNeeded = $true - ActionAfterReboot = 'ContinueConfiguration' + ActionAfterReboot = 'ContinueConfiguration' } } } @@ -963,7 +963,7 @@ If (-not $isAdmin) { WriteInfoHighlighted "`t Creating DSC Configs for DC" LCMConfig -OutputPath "$PSScriptRoot\Temp\config" -ConfigurationData $ConfigData DCHydration -OutputPath "$PSScriptRoot\Temp\config" -ConfigurationData $ConfigData -safemodeAdministratorCred $cred -domainCred $cred -NewADUserCred $cred - + #copy DSC MOF files to DC WriteInfoHighlighted "`t Copying DSC configurations (pending.mof and metaconfig.mof)" New-item -type directory -Path "$PSScriptRoot\Temp\config" -ErrorAction Ignore @@ -979,7 +979,7 @@ If (-not $isAdmin) { WriteInfoHighlighted "`t Starting DC" $DC | Start-VM - $VMStartupTime = 250 + $VMStartupTime = 250 WriteInfoHighlighted "`t Configuring DC using DSC takes a while." WriteInfo "`t `t Initial configuration in progress. Sleeping $VMStartupTime seconds" Start-Sleep $VMStartupTime @@ -991,7 +991,7 @@ If (-not $isAdmin) { Start-Sleep 10 }elseif ($test.status -ne "Success" -and $i -eq 1) { WriteInfo "`t `t Current DSC state: $($test.status), ResourncesNotInDesiredState: $($test.resourcesNotInDesiredState.count), ResourncesInDesiredState: $($test.resourcesInDesiredState.count)." - WriteInfoHighlighted "`t `t Invoking DSC Configuration again" + WriteInfoHighlighted "`t `t Invoking DSC Configuration again" Invoke-Command -VMGuid $DC.id -ScriptBlock {Start-DscConfiguration -UseExisting} -Credential $cred $i++ }elseif ($test.status -ne "Success" -and $i -gt 1) { @@ -1000,7 +1000,7 @@ If (-not $isAdmin) { Invoke-Command -VMGuid $DC.id -ScriptBlock {Restart-Computer} -Credential $cred }elseif ($test.status -eq "Success" ) { WriteInfo "`t `t Current DSC state: $($test.status), ResourncesNotInDesiredState: $($test.resourcesNotInDesiredState.count), ResourncesInDesiredState: $($test.resourcesInDesiredState.count)." - WriteInfoHighlighted "`t `t DSC Configured DC Successfully" + WriteInfoHighlighted "`t `t DSC Configured DC Successfully" } }until ($test.Status -eq 'Success' -and $test.rebootrequested -eq $false) $test @@ -1010,7 +1010,7 @@ If (-not $isAdmin) { Param($LabConfig); redircmp "OU=$($LabConfig.DefaultOUName),$($LabConfig.DN)" Add-DnsServerPrimaryZone -NetworkID ($DHCPscope+"/24") -ReplicationScope "Forest" - } + } #install SCVMM or its prereqs if specified so if (($LabConfig.InstallSCVMM -eq "Yes") -or ($LabConfig.InstallSCVMM -eq "SQL") -or ($LabConfig.InstallSCVMM -eq "ADK") -or ($LabConfig.InstallSCVMM -eq "Prereqs")){ $DC | Add-VMHardDiskDrive -Path $toolsVHD.Path @@ -1043,7 +1043,7 @@ If (-not $isAdmin) { Start-Sleep 30 #Wait as sometimes VMM failed to install without this. Invoke-Command -VMGuid $DC.id -Credential $cred -ScriptBlock { Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process -Force - d:\scvmm\3_SCVMM_Install.ps1 + d:\scvmm\3_SCVMM_Install.ps1 } } @@ -1051,7 +1051,7 @@ If (-not $isAdmin) { WriteInfoHighlighted "Installing SQL" Invoke-Command -VMGuid $DC.id -Credential $cred -ScriptBlock { Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process -Force - d:\scvmm\1_SQL_Install.ps1 + d:\scvmm\1_SQL_Install.ps1 } } @@ -1084,7 +1084,7 @@ If (-not $isAdmin) { #cleanup DC if (-not $DCFilesExists){ WriteInfoHighlighted "Backup DC and cleanup" - #shutdown DC + #shutdown DC WriteInfo "`t Disconnecting VMNetwork Adapter from DC" $DC | Get-VMNetworkAdapter | Disconnect-VMNetworkAdapter WriteInfo "`t Shutting down DC" @@ -1120,7 +1120,7 @@ If (-not $isAdmin) { <# 0 #> New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Cleanup .\Temp\ 1_Prereq.ps1 2_CreateParentDisks.ps1 and rename 3_deploy.ps1 to just deploy.ps1" <# 1 #> New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Keep files (in case DC was not created sucessfully)" ) - + If (!$LabConfig.AutoCleanUp) { $response = $host.UI.PromptForChoice("Unnecessary files cleanup","Do you want to cleanup unnecessary files and folders?", $options, 0 <#default option#>) } @@ -1134,11 +1134,11 @@ If (-not $isAdmin) { }else{ $renamed = $true WriteInfo "`t `t Cleaning unnecessary items" - Remove-Item -Path "$PSScriptRoot\temp" -Force -Recurse + Remove-Item -Path "$PSScriptRoot\temp" -Force -Recurse "$PSScriptRoot\Temp","$PSScriptRoot\1_Prereq.ps1","$PSScriptRoot\2_CreateParentDisks.ps1" | ForEach-Object { WriteInfo "`t `t `t Removing $_" Remove-Item -Path $_ -Force -Recurse -ErrorAction SilentlyContinue - } + } WriteInfo "`t `t `t Renaming $PSScriptRoot\3_Deploy.ps1 to Deploy.ps1" Rename-Item -Path "$PSScriptRoot\3_Deploy.ps1" -NewName "Deploy.ps1" -ErrorAction SilentlyContinue } @@ -1198,13 +1198,13 @@ If (-not $isAdmin) { $vhdProperties['vhd.os.language'] = $OSLanguage } } - $events += New-TelemetryEvent -Event "CreateParentDisks.Vhd" -Metrics $vhdMetrics -Properties $vhdProperties -NickName $LabConfig.TelemetryNickName + $events += Initialize-TelemetryEvent -Event "CreateParentDisks.Vhd" -Metrics $vhdMetrics -Properties $vhdProperties -NickName $LabConfig.TelemetryNickName } # and one overall - $events += New-TelemetryEvent -Event "CreateParentDisks.End" -Metrics $metrics -Properties $properties -NickName $LabConfig.TelemetryNickName + $events += Initialize-TelemetryEvent -Event "CreateParentDisks.End" -Metrics $metrics -Properties $properties -NickName $LabConfig.TelemetryNickName - Send-TelemetryEvents -Events $events | Out-Null + Send-TelemetryEvent -Events $events | Out-Null } Stop-Transcript diff --git a/Scripts/3_Deploy.ps1 b/Scripts/3_Deploy.ps1 index 0abf18f8..e36aa092 100644 --- a/Scripts/3_Deploy.ps1 +++ b/Scripts/3_Deploy.ps1 @@ -4,9 +4,9 @@ If (-not $isAdmin) { Write-Host "-- Restarting as Administrator" -ForegroundColor Cyan ; Start-Sleep -Seconds 1 if($PSVersionTable.PSEdition -eq "Core") { - Start-Process pwsh.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs + Start-Process pwsh.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs } else { - Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs + Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs } exit @@ -58,8 +58,8 @@ If (-not $isAdmin) { true - true - true + true + true $TimeZone @@ -74,7 +74,7 @@ If (-not $isAdmin) { $RunSynchronous - + @@ -82,11 +82,11 @@ If (-not $isAdmin) { Set-Content $unattendFile $fileContent #return the file object - $unattendFile + $unattendFile } Function CreateUnattendFileNoDjoin{ - #Create Unattend(without domain join) + #Create Unattend(without domain join) param ( [parameter(Mandatory=$true)] [string] @@ -136,8 +136,8 @@ If (-not $isAdmin) { true - true - true + true + true $TimeZone @@ -148,7 +148,7 @@ If (-not $isAdmin) { Set-Content $unattendFile $fileContent #return the file object - $unattendFile + $unattendFile } Function CreateUnattendFileWin2012{ @@ -210,8 +210,8 @@ If (-not $isAdmin) { true - true - true + true + true $TimeZone @@ -222,7 +222,7 @@ If (-not $isAdmin) { Set-Content $unattendFile $fileContent #return the file object - $unattendFile + $unattendFile } Function AdditionalLocalAccountXML{ @@ -251,10 +251,10 @@ If (-not $isAdmin) { "@ } - function Get-WindowsBuildNumber { + function Get-WindowsBuildNumber { $os = Get-CimInstance -ClassName Win32_OperatingSystem - return [int]($os.BuildNumber) - } + return [int]($os.BuildNumber) + } Function Set-VMNetworkConfiguration { #source:http://www.ravichaganti.com/blog/?p=2766 with some changes @@ -297,7 +297,7 @@ If (-not $isAdmin) { [Switch]$Dhcp ) - $VM = Get-CimInstance -Namespace "root\virtualization\v2" -ClassName "Msvm_ComputerSystem" | Where-Object ElementName -eq $NetworkAdapter.VMName + $VM = Get-CimInstance -Namespace "root\virtualization\v2" -ClassName "Msvm_ComputerSystem" | Where-Object ElementName -eq $NetworkAdapter.VMName $VMSettings = Get-CimAssociatedInstance -InputObject $vm -ResultClassName "Msvm_VirtualSystemSettingData" | Where-Object VirtualSystemType -EQ "Microsoft:Hyper-V:System:Realized" $VMNetAdapters = Get-CimAssociatedInstance -InputObject $VMSettings -ResultClassName "Msvm_SyntheticEthernetPortSettingData" @@ -339,7 +339,7 @@ If (-not $isAdmin) { } function WrapProcess{ - #Using this function you can run legacy program and search in output string + #Using this function you can run legacy program and search in output string #Example: WrapProcess -filename fltmc.exe -arguments "attach svhdxflt e:" -outputstring "Success" [CmdletBinding()] [Alias()] @@ -382,11 +382,11 @@ If (-not $isAdmin) { # test if process is still running if(!$process.HasExited){ do{ - Start-Sleep 1 + Start-Sleep 1 }until ($process.HasExited -eq $true) } - # get output + # get output $out = $process.StandardOutput.ReadToEnd() if ($out.Contains($outputstring)) { @@ -408,7 +408,7 @@ If (-not $isAdmin) { WriteInfoHighlighted "Creating VM $($VMConfig.VMName)" WriteInfo "`t Looking for Parent Disk" $serverparent = Get-ChildItem "$PSScriptRoot\ParentDisks\" -Recurse | Where-Object Name -eq $VMConfig.ParentVHD - + if ($serverparent -eq $null) { WriteErrorAndExit "Server parent disk $($VMConfig.ParentVHD) not found." }else{ @@ -423,7 +423,7 @@ If (-not $isAdmin) { } WriteInfo "`t Creating OS VHD" New-VHD -ParentPath $serverparent.fullname -Path $vhdpath - + $VMTemp = New-VM -Path "$LabFolder\VMs" -Name $VMname -Generation 2 -MemoryStartupBytes $VMConfig.MemoryStartupBytes -SwitchName $SwitchName -VHDPath $vhdPath #Set dynamic memory @@ -439,16 +439,16 @@ If (-not $isAdmin) { $VMTemp | Set-VM -AutomaticCheckpointsEnabled $False } $VMTemp | Set-VMFirmware -EnableSecureBoot Off - + # only Debian Buster supports Secure Boot #$vm | Set-VMFirmware -EnableSecureBoot On -SecureBootTemplateId "272e7447-90a4-4563-a4b9-8e4ab00526ce" # -SecureBootTemplate MicrosoftUEFICertificateAuthority - + Start-VM $VMTemp # wait for the IP address Write-Host "`t Waiting for network connectivity to the VM..." -NoNewLine $count = 0 - do { + do { $ip = $VMTemp | Get-VMNetworkAdapter | Select-Object -ExpandProperty IPAddresses Start-Sleep -Seconds 1 @@ -485,12 +485,12 @@ If (-not $isAdmin) { if(-not $VMConfig.LinuxDomainJoin -or $VMConfig.LinuxDomainJoin.ToLower() -eq "sssd") { WriteInfo "`t Creating AD Computer object..." Invoke-Command -VMGuid $DC.id -Credential $cred -ArgumentList $VMConfig.VMName,$path,$Labconfig -ScriptBlock { - param($Name,$path,$Labconfig); + param($Name,$path,$Labconfig); New-ADComputer -Name $Name -Path "OU=$($Labconfig.DefaultOUName),$($Labconfig.DN)" $password = ConvertTo-SecureString -String $Name -AsPlainText -Force Get-ADComputer -Identity $Name | Set-ADAccountPassword -NewPassword:$password -Reset:$true - } + } WriteInfo "`t Joining to AD..." $upn = ("$(($LabConfig.DomainAdminName).ToLower())@$($LabConfig.DomainName)") @@ -503,12 +503,12 @@ If (-not $isAdmin) { # Wait for vm to shut down $count = 0 - do { + do { $vm = $VMTemp | Get-VM Start-Sleep -Seconds 1 $count += 1 } while ($vm.State -ne "Off" -and $count -le 60) - + if($vm.State -ne "Off") { $VMTemp | Stop-VM } @@ -530,7 +530,7 @@ If (-not $isAdmin) { WriteInfoHighlighted "Creating VM $($VMConfig.VMName)" WriteInfo "`t Looking for Parent Disk" $serverparent=Get-ChildItem "$PSScriptRoot\ParentDisks\" -Recurse | Where-Object Name -eq $VMConfig.ParentVHD - + if ($serverparent -eq $null){ WriteErrorAndExit "Server parent disk $($VMConfig.ParentVHD) not found." }else{ @@ -554,7 +554,7 @@ If (-not $isAdmin) { if ($VMConfig.Generation -eq 1){ $VMTemp=New-VM -Name $VMname -VHDPath $vhdpath -MemoryStartupBytes $VMConfig.MemoryStartupBytes -path "$LabFolder\VMs" -SwitchName $SwitchName -Generation 1 }else{ - $VMTemp=New-VM -Name $VMname -VHDPath $vhdpath -MemoryStartupBytes $VMConfig.MemoryStartupBytes -path "$LabFolder\VMs" -SwitchName $SwitchName -Generation 2 + $VMTemp=New-VM -Name $VMname -VHDPath $vhdpath -MemoryStartupBytes $VMConfig.MemoryStartupBytes -path "$LabFolder\VMs" -SwitchName $SwitchName -Generation 2 } $VMTemp | Set-VMMemory -DynamicMemoryEnabled $true $VMTemp | Get-VMNetworkAdapter | Rename-VMNetworkAdapter -NewName Management1 @@ -805,7 +805,7 @@ If (-not $isAdmin) { if ($unattendFile){ WriteInfo "`t Adding unattend to VHD" Mount-WindowsImage -Path $mountdir -ImagePath $VHDPath -Index 1 - Use-WindowsUnattend -Path $mountdir -UnattendPath $unattendFile + Use-WindowsUnattend -Path $mountdir -UnattendPath $unattendFile #&"$PSScriptRoot\Tools\dism\dism" /mount-image /imagefile:$vhdpath /index:1 /MountDir:$mountdir #&"$PSScriptRoot\Tools\dism\dism" /image:$mountdir /Apply-Unattend:$unattendfile New-item -type directory "$mountdir\Windows\Panther" -ErrorAction Ignore @@ -863,7 +863,6 @@ If (-not $isAdmin) { } Send-TelemetryEvent -Event "Deploy.Start" -NickName $LabConfig.TelemetryNickName | Out-Null } - #endregion #region Set variables @@ -904,7 +903,7 @@ If (-not $isAdmin) { WriteInfo "`t Prefix used in lab is $($labconfig.prefix)" $SwitchName=($labconfig.prefix+$LabConfig.SwitchName) - WriteInfo "`t Switchname is $SwitchName" + WriteInfo "`t Switchname is $SwitchName" WriteInfo "`t Workdir is $PSScriptRoot" @@ -922,7 +921,7 @@ If (-not $isAdmin) { Get-CimInstance -ClassName "win32_processor" | ForEach-Object { $global:NumberOfLogicalProcessors += $_.NumberOfLogicalProcessors } #Calculate highest VLAN (for additional subnets) - [int]$HighestVLAN=$LabConfig.AllowedVLANs -split "," -split "-" | Select -Last 1 + [int]$HighestVLAN=$LabConfig.AllowedVLANs -split "," -split "-" | Select-Object -Last 1 #Grab defined Management Subnet IDs and ignore 0 $ManagementSubnetIDs=$labconfig.vms.ManagementSubnetID + $LabConfig.ManagementSubnetIDs | Select-Object -Unique | Sort-Object | Where-Object {$_ -ne 0} @@ -991,7 +990,7 @@ If (-not $isAdmin) { WriteInfo "`t Installing Failover Clustering Feature" $FC=Install-WindowsFeature Failover-Clustering If ($FC.Success -eq $True){ - WriteSuccess "`t`t Failover Clustering Feature installed with exit code: $($FC.ExitCode)" + WriteSuccess "`t`t Failover Clustering Feature installed with exit code: $($FC.ExitCode)" }else{ WriteError "`t`t Failover Clustering Feature was not installed with exit code: $($FC.ExitCode)" } @@ -1008,10 +1007,10 @@ If (-not $isAdmin) { } } - WriteInfoHighlighted "Adding svhdxflt to registry for autostart" + WriteInfoHighlighted "Adding svhdxflt to registry for autostart" if (!(Test-Path HKLM:\SYSTEM\CurrentControlSet\Services\svhdxflt\Parameters)){ New-Item HKLM:\SYSTEM\CurrentControlSet\Services\svhdxflt\Parameters - } + } New-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\svhdxflt\Parameters -Name AutoAttachOnNonCSVVolumes -PropertyType DWORD -Value 1 -force } @@ -1043,7 +1042,7 @@ If (-not $isAdmin) { WriteInfoHighlighted "Checking if volume filesystem is NTFS or ReFS" $driveletter=$PSScriptRoot -split ":" | Select-Object -First 1 if ($PSScriptRoot -like "c:\ClusterStorage*"){ - WriteSuccess "`t Volume Cluster Shared Volume. Mountdir will be $env:Temp\MSLabMountdir" + WriteSuccess "`t Volume Cluster Shared Volume. Mountdir will be $env:Temp\MSLabMountdir" $mountdir="$env:Temp\MSLabMountDir" $VolumeFileSystem="CSVFS" }else{ @@ -1068,7 +1067,7 @@ If (-not $isAdmin) { WriteInfoHighlighted "Creating Switch" WriteInfo "`t Checking if $SwitchName already exists..." - if (-not (Get-VMSwitch -Name $SwitchName -ErrorAction Ignore)){ + if (-not (Get-VMSwitch -Name $SwitchName -ErrorAction Ignore)){ WriteInfo "`t Creating $SwitchName..." if ($LabConfig.SwitchNICs){ #test if NICs are not already connected to another switch @@ -1144,7 +1143,7 @@ If (-not $isAdmin) { if ($TempNetAdapters.name.count -gt 1){ WriteInfo "`t More than 1 NIC detected" WriteInfoHighlighted "`t Please select NetAdapter you want to use for vSwitch" - $tempNetAdapter=get-netadapter | Where-Object Name -NotLike vEthernet* | Where-Object status -eq up | Out-GridView -OutputMode Single -Title "Please select adapter you want to use for External vSwitch" + $tempNetAdapter=get-netadapter | Where-Object Name -NotLike vEthernet* | Where-Object status -eq up | Out-GridView -OutputMode Single -Title "Please select adapter you want to use for External vSwitch" if (!$tempNetAdapter){ WriteErrorAndExit "You did not select any net adapter. Exitting." } @@ -1204,8 +1203,8 @@ If (-not $isAdmin) { $toolsVHD=New-VHD -Path "$PSScriptRoot\ParentDisks\tools.vhdx" -SizeBytes 30GB -Dynamic #mount and format VHD $VHDMount = Mount-VHD $toolsVHD.Path -Passthru - $vhddisk = $VHDMount| get-disk - $vhddisk | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -UseMaximumSize -AssignDriveLetter |Format-Volume -FileSystem NTFS -AllocationUnitSize 8kb -NewFileSystemLabel ToolsDisk + $vhddisk = $VHDMount| get-disk + $vhddisk | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -UseMaximumSize -AssignDriveLetter |Format-Volume -FileSystem NTFS -AllocationUnitSize 8kb -NewFileSystemLabel ToolsDisk #dismount VHD Dismount-VHD $vhddisk.Number }else{ @@ -1272,7 +1271,7 @@ If (-not $isAdmin) { $DC | Add-VMNetworkAdapter -SwitchName $SwitchName -Name $AdditionalNetworkConfig.NetName WriteInfo "`t`t Adding Adapter $($AdditionalNetworkConfig.NetName) with IP $($AdditionalNetworkConfig.NetAddress)$global:IP" $DC | Get-VMNetworkAdapter -Name $AdditionalNetworkConfig.NetName | Set-VMNetworkConfiguration -IPAddress "$($AdditionalNetworkConfig.NetAddress)$global:IP" -Subnet $AdditionalNetworkConfig.Subnet - if($AdditionalNetworkConfig.NetVLAN -ne 0){ + if($AdditionalNetworkConfig.NetVLAN -ne 0){ $DC | Get-VMNetworkAdapter -Name $AdditionalNetworkConfig.NetName | Set-VMNetworkAdapterVlan -VlanId $AdditionalNetworkConfig.NetVLAN -Access } } @@ -1388,7 +1387,7 @@ If (-not $isAdmin) { $IP="192.168.0.$startIP" WriteInfo "`t Configure static IP $IP on Internet NIC" Invoke-Command -VMGuid $DC.id -Credential $cred -ScriptBlock { - $NetAdapterName=(Get-NetAdapterAdvancedProperty | where displayvalue -eq Internet).Name + $NetAdapterName=(Get-NetAdapterAdvancedProperty | Where-Object displayvalue -eq Internet).Name New-NetIPAddress -InterfaceAlias $NetAdapterName -IPAddress $using:IP -PrefixLength 24 -DefaultGateway "192.168.0.1" } } @@ -1513,7 +1512,7 @@ If (-not $isAdmin) { WriteInfoHighlighted "Creating DSC config to configure DC as pull server" [DSCLocalConfigurationManager()] - Configuration PullClientConfig + Configuration PullClientConfig { param ( @@ -1536,7 +1535,7 @@ If (-not $isAdmin) { ActionAfterReboot = 'ContinueConfiguration' } - ConfigurationRepositoryWeb PullServerWeb { + ConfigurationRepositoryWeb PullServerWeb { ServerURL = "http://dc.$($DomainName):8080/PSDSCPullServer.svc" AllowUnsecureConnection = $true RegistrationKey = '14fc8e72-5036-4e79-9f89-5382160053aa' @@ -1573,7 +1572,7 @@ If (-not $isAdmin) { if(-not $VMConfig.MemoryStartupBytes) { $VMConfig.MemoryStartupBytes = 512MB } - + #create VM with Shared configuration if ($VMConfig.configuration -eq 'Shared'){ #create disks (if not already created) @@ -1581,11 +1580,11 @@ If (-not $isAdmin) { if (!(Test-Path -Path "$LABfolder\VMs\*$VMSet*.VHDS")){ $SharedSSDs=$null $SharedHDDs=$null - If (($VMConfig.SSDNumber -ge 1) -and ($VMConfig.SSDNumber -ne $null)){ + If (($VMConfig.SSDNumber -ge 1) -and ($VMConfig.SSDNumber -ne $null)){ $SharedSSDs= 1..$VMConfig.ssdnumber | ForEach-Object {New-vhd -Path "$LABfolder\VMs\SharedSSD-$VMSet-$_.VHDS" -Dynamic -Size $VMConfig.SSDSize} $SharedSSDs | ForEach-Object {WriteInfo "`t Disk SSD $($_.path) size $($_.size /1GB)GB created"} } - If (($VMConfig.HDDNumber -ge 1) -and ($VMConfig.HDDNumber -ne $null)){ + If (($VMConfig.HDDNumber -ge 1) -and ($VMConfig.HDDNumber -ne $null)){ $SharedHDDs= 1..$VMConfig.hddnumber | ForEach-Object {New-VHD -Path "$LABfolder\VMs\SharedHDD-$VMSet-$_.VHDS" -Dynamic -Size $VMConfig.HDDSize} $SharedHDDs | ForEach-Object {WriteInfo "`t Disk HDD $($_.path) size $($_.size /1GB)GB created"} } @@ -1630,7 +1629,7 @@ If (-not $isAdmin) { #Add disks #add "SSDs" - If (($VMConfig.SSDNumber -ge 1) -and ($VMConfig.SSDNumber -ne $null)){ + If (($VMConfig.SSDNumber -ge 1) -and ($VMConfig.SSDNumber -ne $null)){ $SSDs= 1..$VMConfig.SSDNumber | ForEach-Object { New-vhd -Path "$LabFolder\VMs\$VMname\Virtual Hard Disks\SSD-$_.VHDX" -Dynamic -Size $VMConfig.SSDSize} WriteInfoHighlighted "`t Adding Virtual SSD Disks" $SSDs | ForEach-Object { @@ -1651,7 +1650,7 @@ If (-not $isAdmin) { } } - #create VM with Replica configuration + #create VM with Replica configuration if ($VMConfig.configuration -eq 'Replica'){ #create shared drives if not already created $VMSet=$VMConfig.VMSet @@ -1668,7 +1667,7 @@ If (-not $isAdmin) { $createdVm = BuildVM -VMConfig $VMConfig -LabConfig $labconfig -LabFolder $LABfolder #Add disks - $VMname=$Labconfig.Prefix+$VMConfig.VMName + $VMname=$Labconfig.Prefix+$VMConfig.VMName WriteInfoHighlighted "`t Attaching Shared Disks..." #Add HDD $ReplicaHdd | ForEach-Object { @@ -1692,7 +1691,7 @@ If (-not $isAdmin) { } if((Test-Path -Path $createdVm.OSDiskPath) -and $VMConfig.configuration -ne "Linux") { $osInfo = Get-WindowsImage -ImagePath $createdVm.OSDiskPath -Index 1 - + $properties.'vm.os.installationType' = $osInfo.InstallationType $properties.'vm.os.editionId' = $osInfo.EditionId $properties.'vm.os.version' = $osInfo.Version @@ -1701,10 +1700,10 @@ If (-not $isAdmin) { $metrics = @{ 'vm.deploymentDuration' = ((Get-Date) - $vmProvisioningStartTime).TotalSeconds } - $vmInfo = New-TelemetryEvent -Event "Deploy.VM" -Properties $properties -Metrics $metrics -NickName $LabConfig.TelemetryNickName + $vmInfo = Initialize-TelemetryEvent -Event "Deploy.VM" -Properties $properties -Metrics $metrics -NickName $LabConfig.TelemetryNickName $vmDeploymentEvents += $vmInfo } - + $provisionedVMs += $createdVm.VM } } @@ -1712,7 +1711,7 @@ If (-not $isAdmin) { #endregion #region Finishing - WriteInfoHighlighted "Finishing..." + WriteInfoHighlighted "Finishing..." #a bit cleanup Remove-Item -Path "$PSScriptRoot\temp" -Force -Recurse @@ -1721,7 +1720,7 @@ If (-not $isAdmin) { WriteInfo "`t Setting MacSpoofing On and AllowTeaming On" Set-VMNetworkAdapter -VMName "$($labconfig.Prefix)*" -MacAddressSpoofing On -AllowTeaming On - #list VMs + #list VMs $AllVMs = Get-VM | Where-Object name -like "$($labconfig.Prefix)*" $AllVMs | ForEach-Object { WriteSuccess "Machine $($_.VMName) provisioned" } @@ -1732,7 +1731,7 @@ If (-not $isAdmin) { #Enable Guest services on all VMs if integration component if configured if ($labconfig.EnableGuestServiceInterface){ WriteInfo "`t Enabling Guest Service Interface" - $vms = Get-VM -VMName "$($labconfig.Prefix)*" | Where-Object {$_.state -eq "Running" -or $_.state -eq "Off"} + $vms = Get-VM -VMName "$($labconfig.Prefix)*" | Where-Object {$_.state -eq "Running" -or $_.state -eq "Off"} foreach ($vm in $vms) { $guestServiceId = 'Microsoft:{0}\6C09BB55-D683-4DA0-8931-C9BF705F6480' -f $vm.Id $guestService = $vm | Get-VMIntegrationService | Where-Object -FilterScript {$_.Id -eq $guestServiceId} @@ -1764,7 +1763,7 @@ If (-not $isAdmin) { <# 0 #> New-Object System.Management.Automation.Host.ChoiceDescription "&No", "No VM will be started." <# 1 #> New-Object System.Management.Automation.Host.ChoiceDescription "&All", "All VMs in the lab will be started." ) - + if($provisionedVMs.Count -gt 0) { <# 2 #> $options += New-Object System.Management.Automation.Host.ChoiceDescription "&Deployed only", "Only newly deployed VMs will be started." } @@ -1780,10 +1779,10 @@ If (-not $isAdmin) { $toStart = $provisionedVMs } } - + if(($toStart | Measure-Object).Count -gt 0) { WriteInfoHighlighted "Starting VMs" - $toStart | ForEach-Object { + $toStart | ForEach-Object { WriteInfo "`t $($_.Name)" Start-VM -VM $_ -WarningAction SilentlyContinue } @@ -1802,10 +1801,10 @@ If (-not $isAdmin) { 'lab.isncrementalDeployment' = $LABExists 'lab.autostartmode' = $startVMs } - $telemetryEvent = New-TelemetryEvent -Event "Deploy.End" -Metrics $metrics -Properties $properties -NickName $LabConfig.TelemetryNickName + $telemetryEvent = Initialize-TelemetryEvent -Event "Deploy.End" -Metrics $metrics -Properties $properties -NickName $LabConfig.TelemetryNickName $vmDeploymentEvents += $telemetryEvent - Send-TelemetryEvents -Events $vmDeploymentEvents | Out-Null + Send-TelemetryEvent -Events $vmDeploymentEvents | Out-Null } #write how much it took to deploy diff --git a/Scripts/Cleanup.ps1 b/Scripts/Cleanup.ps1 index ea5b5c08..e318a080 100644 --- a/Scripts/Cleanup.ps1 +++ b/Scripts/Cleanup.ps1 @@ -4,9 +4,9 @@ If (-not $isAdmin) { Write-Host "-- Restarting as Administrator" -ForegroundColor Cyan ; Start-Sleep -Seconds 1 if($PSVersionTable.PSEdition -eq "Core") { - Start-Process pwsh.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs + Start-Process pwsh.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs } else { - Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs + Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs } exit @@ -62,7 +62,7 @@ If (-not $isAdmin) { #just one more space WriteInfo "" - # This is only needed if you kill deployment script in middle when it mounts VHD into mountdir. + # This is only needed if you kill deployment script in middle when it mounts VHD into mountdir. if ((Get-ChildItem -Path "$PSScriptRoot\temp\mountdir" -ErrorAction SilentlyContinue)){ Dismount-WindowsImage -Path "$PSScriptRoot\temp\mountdir" -Discard -ErrorAction SilentlyContinue } @@ -105,18 +105,18 @@ If (-not $isAdmin) { WriteInfo "`t Removing vSwitch $($extvSwitch.Name)" $extvSwitch | Remove-VMSwitch -Force } - #Cleanup folders + #Cleanup folders if ((test-path "$PSScriptRoot\LAB\VMs") -or (test-path "$PSScriptRoot\temp") ){ WriteInfoHighlighted "Cleaning folders" "$PSScriptRoot\LAB\VMs","$PSScriptRoot\temp" | ForEach-Object { if ((test-path -Path $_)){ WriteInfo "`t Removing folder $_" remove-item $_ -Confirm:$False -Recurse - } + } } } - #Unzipping configuration files as VM was removed few lines ago-and it deletes vm configuration... + #Unzipping configuration files as VM was removed few lines ago-and it deletes vm configuration... $zipfile = "$PSScriptRoot\LAB\DC\Virtual Machines.zip" $zipoutput = "$PSScriptRoot\LAB\DC\" Microsoft.PowerShell.Archive\Expand-Archive -Path $zipfile -DestinationPath $zipoutput -Force @@ -131,7 +131,7 @@ If (-not $isAdmin) { Send-TelemetryEvent -Event "Cleanup" -Metrics $metrics -NickName $LabConfig.TelemetryNickName | Out-Null } - #finishing + #finishing WriteSuccess "Job Done! Press enter to continue ..." $exit=Read-Host }else { diff --git a/Scripts/LabConfig.ps1 b/Scripts/LabConfig.ps1 index cde81bfc..dfa3efaa 100644 --- a/Scripts/LabConfig.ps1 +++ b/Scripts/LabConfig.ps1 @@ -36,7 +36,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M ServerMSUsFolder=""; # (Optional) If configured, script will inject all MSU files found into server OS EnableGuestServiceInterface=$false; # (Optional) If True, then Guest Services integration component will be enabled on all VMs. DCVMProcessorCount=2; # (Optional) 2 is default. If specified more/less, processorcount will be modified. - DHCPscope="10.0.0.0"; # (Optional) 10.0.0.0 is configured if nothing is specified. Scope has to end with .0 (like 10.10.10.0). It's always /24 + DHCPscope="10.0.0.0"; # (Optional) 10.0.0.0 is configured if nothing is specified. Scope has to end with .0 (like 10.10.10.0). It's always /24 DCVMVersion="9.0"; # (Optional) Latest is used if nothing is specified. Make sure you use values like "8.0","8.3","9.0" TelemetryLevel=""; # (Optional) If configured, script will stop prompting you for telemetry. Values are "None","Basic","Full" TelemetryNickname=""; # (Optional) If configured, telemetry will be sent with NickName to correlate data to specified NickName. So when leaderboards will be published, MSLab users will be able to see their own stats @@ -53,7 +53,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M } # Specifying LabVMs - 1..4 | ForEach-Object { + 1..4 | ForEach-Object { $VMNames="S2D"; # Here you can bulk edit name of 4 VMs created. In this case will be s2d1,s2d2,s2d3,s2d4 created $LABConfig.VMs += @{ VMName = "$VMNames$_" ; @@ -68,7 +68,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M } #optional: (only if AdditionalNetworks are configured in $LabConfig.VMs) this is just an example. In this configuration its not needed. - $LABConfig.AdditionalNetworksConfig += @{ + $LABConfig.AdditionalNetworksConfig += @{ NetName = 'Storage1'; # Network Name NetAddress='172.16.1.'; # Network Addresses prefix. (starts with 1), therefore first VM with Additional network config will have IP 172.16.1.1 NetVLAN='1'; # VLAN tagging @@ -109,7 +109,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M 'SQL' - installs just SQL 'Prereqs' - installs ADK and SQL 'No' - No, or anything else, nothing is installed. - + *requires install files in toolsVHD\SCVMM\, or it will fail. You can download all tools here: SQL: http://www.microsoft.com/en-us/evalcenter/evaluate-sql-server-2016 SCVMM: http://www.microsoft.com/en-us/evalcenter/evaluate-system-center-technical-preview @@ -145,7 +145,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M It will add vNIC to DC and configure NAT with some Open DNS servers in DNS forwarder UseHostDnsAsForwarder (Optional) - If $true, local DNS servers will be used as DNS forwarders in DC when Internet is enabled. + If $true, local DNS servers will be used as DNS forwarders in DC when Internet is enabled. By default local host's DNS servers will be used as forwarders. CustomDnsForwarders (Optional) @@ -153,7 +153,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M If not defined at all, commonly known DNS servers will be used as a fallback: - Google DNS: 8.8.8.8 - Cloudfare: 1.1.1.1 - + DHCPscope (Optional) If configured, a custom DHCP scope will be used. Will always use a '/24'. Specify input like '10.1.0.0' or '192.168.0.0' @@ -196,11 +196,11 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M ManagementSubnetIDs Example: ManagementSubnetIDs=0..3 If configured, it will add another management subnet. For example if configured 0..3, it will add 3 more subnets 10.0.1.0/24 to 10.0.3.0/24 on VLANs that 11,12, and 13. (Because allowed VLANs are 1-10) - + Linux (optional) Example: Linux=$true If set to $true, additional prerequisities (SSH Client, SSH Key, Packer, Packer templates) required for building Linux images will be downloaded and configured. - + LinuxAdminName (optional) Example: LinuxAdminName="linuxadmin" If set, local user account with that name will be created in Linux image. If not, DomainAdminName will be used as a local account. @@ -227,7 +227,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M $LABConfig.VMs += @{ VMName = 'Management' ; Configuration = 'Simple' ; ParentVHD = 'Win10_G2.vhdx' ; MemoryStartupBytes= 1GB ; AddToolsVHD=$True } Multiple: 1..2 | ForEach-Object { $VMNames="Replica" ; $LABConfig.VMs += @{ VMName = "$VMNames$_" ; Configuration = 'Replica' ; ParentVHD = 'Win2016NanoHV_G2.vhdx' ; ReplicaHDDSize = 20GB ; ReplicaLogSize = 10GB ; MemoryStartupBytes= 2GB ; VMSet= 'ReplicaSet1' ; AdditionalNetworks = $True} } - + VMName (Mandatory) Can be whatever. This name will be used as name to djoin VM. @@ -247,7 +247,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M 'Win10_G2.vhdx' - Windows 10 if you selected to hydrate it with create client parent. AdditionalNetworks (Optional) - $True - Additional networks (configured in AdditonalNetworkConfig) are added + $True - Additional networks (configured in AdditonalNetworkConfig) are added AdditionalNetworkAdapters (Optional) - Hashtable or array if multiple network adapters should be connected to this virtual machine @{ @@ -257,7 +257,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M IpConfiguration (Optional) - DHCP or hastable with specific IP configuration @{ IpAddress (Mandatory) - Static IP Address that would be injected to the OS - Subnet (Mandatory) + Subnet (Mandatory) } } @@ -278,7 +278,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M MemoryMinimumBytes (Optional) Example: 1GB Minimum memory bytes, must be less or equal to MemoryStartupBytes - If not set, default is used. + If not set, default is used. StaticMemory (Optional) if $True, then static memory is configured @@ -344,7 +344,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M ManagementSubnetID (Optional) This will set Management NICs to defined subnet id by configuring native VLAN ID. Default is 0. If configured to 1, it will increase highest allowed VLAN by one and configure. - For example ManagementSubnetID=1, AllowedVlans=10, then ManagementSubnetID VLAN will be configured 11. + For example ManagementSubnetID=1, AllowedVlans=10, then ManagementSubnetID VLAN will be configured 11. #DisableTimeIC (Optional) Example DisableTimeIC=$true @@ -374,9 +374,9 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M <# Just some VMs $LabConfig.VMs = @( - @{ VMName = 'Simple1' ; Configuration = 'Simple' ; ParentVHD = 'Win2016NanoHV_G2.vhdx' ; MemoryStartupBytes= 512MB }, - @{ VMName = 'Simple2' ; Configuration = 'Simple' ; ParentVHD = 'Win2016NanoHV_G2.vhdx' ; MemoryStartupBytes= 512MB }, - @{ VMName = 'Simple3' ; Configuration = 'Simple' ; ParentVHD = 'Win2016NanoHV_G2.vhdx' ; MemoryStartupBytes= 512MB }, + @{ VMName = 'Simple1' ; Configuration = 'Simple' ; ParentVHD = 'Win2016NanoHV_G2.vhdx' ; MemoryStartupBytes= 512MB }, + @{ VMName = 'Simple2' ; Configuration = 'Simple' ; ParentVHD = 'Win2016NanoHV_G2.vhdx' ; MemoryStartupBytes= 512MB }, + @{ VMName = 'Simple3' ; Configuration = 'Simple' ; ParentVHD = 'Win2016NanoHV_G2.vhdx' ; MemoryStartupBytes= 512MB }, @{ VMName = 'Simple4' ; Configuration = 'Simple' ; ParentVHD = 'Win2016NanoHV_G2.vhdx' ; MemoryStartupBytes= 512MB } ) @@ -391,7 +391,7 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'M 1..100 | ForEach-Object {"NanoServer$_"} | ForEach-Object { $LabConfig.VMs += @{ VMName = $_ ; Configuration = 'Simple' ; ParentVHD = 'Win2016NanoHV_G2.vhdx' ; MemoryStartupBytes= 512MB } } 1..100 | ForEach-Object {"Windows10_$_"} | ForEach-Object { $LabConfig.VMs += @{ VMName = $_ ; Configuration = 'Simple' ; ParentVHD = 'Win10_G2.vhdx' ; MemoryStartupBytes= 512MB ; AddToolsVHD=$True ; DisableWCF=$True } } - or Several different VMs + or Several different VMs * you need to provide your GPT VHD for win 2012 (like created with convertwindowsimage script) $LabConfig.VMs += @{ VMName = 'Win10' ; Configuration = 'Simple' ; ParentVHD = 'Win10_G2.vhdx' ; MemoryStartupBytes= 512MB ; DisableWCF=$True ; vTPM=$True ; EnableWinRM=$True } $LabConfig.VMs += @{ VMName = 'Win10_OOBE' ; Configuration = 'Simple' ; ParentVHD = 'Win10_G2.vhdx' ; MemoryStartupBytes= 512MB ; DisableWCF=$True ; vTPM=$True ; Unattend="None" }