From 42218e9477281d06e95665810475d686350e4506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr?= Date: Tue, 21 Jul 2020 22:36:57 +0200 Subject: [PATCH] Github Action & Telemetry (#363) * Introduce Github Action workflow to automate the builds of WSLab scripts * Add support for Telemetry collection to Application Insights * Deprecate Scripts.zip file in the repository in favor of Releases feature in Github Co-authored-by: Jaromir Kaspar Former-commit-id: c373ca5d23b25e6a9f71defd8756018f45c05713 --- .github/workflows/create-release.yml | 96 ++++++++ .gitignore | 2 + Docs/toc.yml | 6 + Docs/wslab-telemetry.md | 121 ++++++++++ README.md | 4 + Scripts/0_Shared.ps1 | 347 +++++++++++++++++++++++++++ Scripts/1_Prereq.ps1 | 53 ++-- Scripts/2_CreateParentDisks.ps1 | 151 +++++++++--- Scripts/3_Deploy.ps1 | 116 ++++++--- Scripts/Cleanup.ps1 | 44 ++-- Scripts/LabConfig.ps1 | 25 +- build.ps1 | 42 ++++ 12 files changed, 875 insertions(+), 132 deletions(-) create mode 100644 .github/workflows/create-release.yml create mode 100644 .gitignore create mode 100644 Docs/toc.yml create mode 100644 Docs/wslab-telemetry.md create mode 100644 Scripts/0_Shared.ps1 create mode 100644 build.ps1 diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 00000000..db91c743 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,96 @@ +name: Create release + +defaults: + run: + shell: powershell + +on: + push: + paths: + - 'Scripts/**' + branches: [ master ] + +jobs: + new-version: + name: Bump version + runs-on: windows-2019 + outputs: + previous_tag: ${{ steps.bump.outputs.previous_tag }} + new_tag: ${{ steps.bump.outputs.new_tag }} + steps: + - uses: actions/checkout@v2 + - 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 + if($hash) { + $currentTag = git describe --tags $hash + $parts = $currentTag.Substring(1) -split '\.' + if($parts[1] -eq $today.ToString("MM") -and $parts[0] -eq $today.ToString("yy")) { $newVersion[2] = ([int]$parts[2] + 1).ToString("0") } + } + + $newTag = "v" + ($newVersion -join ".") + git tag $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 + + new-release: + name: Create release + runs-on: windows-2019 + needs: new-version + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Build scripts + shell: powershell + run: | + ./build.ps1 -Version ${{ needs.new-version.outputs.new_tag }} + + - 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' + "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 }} + 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: wslab_${{ needs.new-version.outputs.new_tag }}.zip + asset_content_type: application/zip diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1e964af5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/Output +Release.zip diff --git a/Docs/toc.yml b/Docs/toc.yml new file mode 100644 index 00000000..0254c20d --- /dev/null +++ b/Docs/toc.yml @@ -0,0 +1,6 @@ +- name: Hydration + href: wslab-hydration.md +- name: Deployment + href: wslab-deployment.md +- name: Telemetry + href: wslab-telemetry.md diff --git a/Docs/wslab-telemetry.md b/Docs/wslab-telemetry.md new file mode 100644 index 00000000..2e29a4b4 --- /dev/null +++ b/Docs/wslab-telemetry.md @@ -0,0 +1,121 @@ +# WSLab Telemetry + +## Introduction + +We started to collect telemetry to better understand impact of WSLab scripts as currently we cannot determine number of VMs deployed and where is WSLab being used. Data are hosted in Azure Application Insights and it is absolutely transparent what information is being collected, since all is visible in PowerShell Scripts. + +Currently there is no public facing interface, however we plan to create PowerBI dashboards, where we will present Leader Boards and some nice statistics - such as how many VMs were deployed and we will be able to create statistics that will show in what countries is WSLab running and many more + +## Verbosity level + +Currently there are 3 different levels: **None**, **Basic** and **Full**. If nothing is configured in LabConfig, you will be asked to provide your preferred option. + +### None + +If you don't want to send anything, or if you are in offline environment. + +### Basic + +Sends information about deployed lab, that is vital for us to understand impact of WSLab scripts. + +### Full + +Provides enhanced information such as computer model, amount of RAM and number of cores. This information is not essential, however i will provide interesting insight. + +## LabConfig examples + +Basic telemetry level + +```powershell +$LabConfig = @{ + DomainAdminName = 'LabAdmin' + AdminPassword = 'LS1setup!' + Prefix = 'WSLab-' + DCEdition = '4' + Internet = $true + TelemetryLevel = 'Basic' + AdditionalNetworksConfig = @() + VMs = @() +} + +``` + +Full telemetry including NickName that will be included in LeaderBoards once we will publish PowerBI statistics. + +```powershell +$LabConfig = @{ + DomainAdminName = 'LabAdmin' + AdminPassword = 'LS1setup!' + Prefix = 'WSLab-' + DCEdition = '4' + Internet = $true + TelemetryLevel = 'Full' + TelemetryNickname = 'Jaromirk' + AdditionalNetworksConfig = @() + VMs = @() +} + +``` + +## Collected information + +These properties are attached to every telemetry event that is sent to the Application Insights workspace. + +| | Basic | Full |Description| Sample Value | Application Insights property | +|---------------------|:-----:|:----:|-----------| --- | ---- | +| Application Version | x | x | Version of WSLab Scripts | v20.07.1 | `ai.application.ver` | +| Telemetry Level | x | x | Which level of telemetry has been set | Full | `telemetry.level` | +| Product type | x | x | Workstation or Server| Workstation | `os.type` | +| Session ID | x | x | One-way hash (`SHA1`) of `MachineGUID`, `PSScriptRoot` and `ComputerName`. Purpose of this session ID is only to link execution of separate scripts within the same lab folder. | 482e33a99e6fb41e5f739d9294ac1b339c7c3c60 | `ai.session.id` | +| Device Locale | x | x | Locale of Host OS | en-US | `ai.device.locale` | +| PowerShell Edition | x | x | Desktop or Core | Core | `powershell.edition` | +| PowerShell Version | x | x | version | 7.0.2 | `powershell.version` | +| TotalDuration | x | x | Duration of script run in seconds | 23,62 | `TotalDuration` | +| Device Manufacturer | | x | Device Manufacturer | LENOVO | `ai.device.oemName` | +| Device model | | x | Device model based on `Win32_ComputerSystem` | ThinkPad P52 | `ai.device.model` | +| Operating System | | x | OS SKU and build | Windows 10 Enterprise (10.0.19041.388)| `ai.device.os` | +| OS Build | | x | OS Build Number | 19041 | `os.build` | +| Amount of RAM | | x | Total amount of RAM in MB | 65311 | `memory.total` | +| Number of Sockets | | x | How many sockets system have | 1 | `cpu.sockets.count` | +| Number of Cores | | x | Total number of CPU cores available | 12 | `cpu.logical.count` | +| Volume Capacity | | x | Capacity of a volume where WSLab was run (in GB) | 954 | `volume.size` | +| Disk Model | | x | Friendly name of a disk where volume with WSLab was run | Samsung SSD 970 PRO 1TB | `disk.model` | +| Disk Media Type | | x | Type of the disk where WSLab was run | SSD | `disk.type` | +| Disk Bus type | | x | Bus connection of the disk where WSLab was run | NVMe | `disk.busType` | + +### Specific Events for 2_CreateParentDisks.ps1 script + +#### CreateParentDisks.Start +When script is started. +#### CreateParentDisks.Vhd +For each hydrated VHD parent disk. +#### CreateParentDisks.End +When script finished. + +### Specific Events to Deploy.ps1 script + +#### Deploy.Start +When script is started. + +#### Deploy.VM +For each provisioned VM. + +#### Deploy.End +When script finished. + +| |Basic|Full|Description| +|-----------------------------|:---:|:--:|-----------| +|VMDeploymentDuration |x |x |Duration of Deploy.ps1 script| +|Deployed VM OSBuild |x |x |For example 19041| +|Deployed VM InstallationType |x |x |For example Server Core| +|Deployed VM OsVersion |x |x |For example 10.0.17763.1282| +|Deployed VM EditionID |x |x |For example ServerDatacenter| + +### Specific to Cleanup.ps1 + +| |Basic|Full|Description| +|-----------|:---:|:--:|-----------| +| lab.removed.count |x |x |Number of removed VMs| + + + diff --git a/README.md b/README.md index faa2f3cb..caf0074a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # WSLab Introduction +| Last Update | Latest Release | | +| --- | --- | -- | +| [![GitHub last commit](https://img.shields.io/github/last-commit/Microsoft/wslab/master.svg?style=flat-square)]() | [![GitHub release](https://img.shields.io/github/release/microsoft/wslab.svg?style=flat-square)](https://aka.ms/wslab/download) | [![Download](https://img.shields.io/static/v1?label=&message=Download+WSLab&color=green&style=for-the-badge)](https://aka.ms/wslab/download) | + - [WSLab Introduction](#wslab-introduction) diff --git a/Scripts/0_Shared.ps1 b/Scripts/0_Shared.ps1 new file mode 100644 index 00000000..1f75974c --- /dev/null +++ b/Scripts/0_Shared.ps1 @@ -0,0 +1,347 @@ +#region Output logging +function WriteInfo($message) { + Write-Host $message +} + +function WriteInfoHighlighted($message) { +Write-Host $message -ForegroundColor Cyan +} + +function WriteSuccess($message) { +Write-Host $message -ForegroundColor Green +} + +function WriteError($message) { +Write-Host $message -ForegroundColor Red +} + +function WriteErrorAndExit($message) { + Write-Host $message -ForegroundColor Red + Write-Host "Press enter to continue ..." + Stop-Transcript + Read-Host | Out-Null + Exit +} +#endregion + +#region Telemetry +Function Merge-Hashtables { + $Output = @{} + ForEach ($Hashtable in ($Input + $Args)) { + If ($Hashtable -is [Hashtable]) { + ForEach ($Key in $Hashtable.Keys) {$Output.$Key = $Hashtable.$Key} + } + } + $Output +} +function Get-StringHash { + [CmdletBinding()] + param ( + [Parameter(ValueFromPipeline, Mandatory = $true)] + [string]$String, + $Hash = "SHA1" + ) + + process { + $bytes = [System.Text.Encoding]::UTF8.GetBytes($String) + $algorithm = [System.Security.Cryptography.HashAlgorithm]::Create($Hash) + $StringBuilder = New-Object System.Text.StringBuilder + + $algorithm.ComputeHash($bytes) | + ForEach-Object { + $null = $StringBuilder.Append($_.ToString("x2")) + } + + $StringBuilder.ToString() + } +} + +function Get-VolumePhysicalDisk { + param( + [Parameter(Mandatory = $true)] + [string]$Volume + ) + + process { + if(-not $Volume.EndsWith(":")) { + $Volume += ":" + } + + $physicalDisks = Get-cimInstance "win32_diskdrive" + foreach($disk in $physicalDisks) { + $partitions = Get-cimInstance -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=`"$($disk.DeviceID.replace('\','\\'))`"} WHERE AssocClass = Win32_DiskDriveToDiskPartition" + foreach($partition in $partitions) { + $partitionVolumes = Get-cimInstance -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=`"$($partition.DeviceID)`"} WHERE AssocClass = Win32_LogicalDiskToPartition" + foreach($partitionVolume in $partitionVolumes) { + if($partitionVolume.Name -eq $Volume) { + $physicalDisk = Get-PhysicalDisk -DeviceNumber $disk.Index + return $physicalDisk + } + } + } + } + } +} + +function Get-TelemetryLevel { + param( + [switch]$OptOut + ) + process { + $acceptedTelemetryLevels = "None", "Basic", "Full" + + # LabConfig value has a priority + if($LabConfig.TelemetryLevel -and $LabConfig.TelemetryLevel -in $acceptedTelemetryLevels) { + return $LabConfig.TelemetryLevel + } + + # Environment variable as a fallback + if($env:WSLAB_TELEMETRY_LEVEL -and $env:WSLAB_TELEMETRY_LEVEL -in $acceptedTelemetryLevels) { + return $env:WSLAB_TELEMETRY_LEVEL + } + + # If nothing is explicitely configured and OptOut flag enabled, explicitely disable telemetry + if($OptOut) { + return "None" + } + + # as a last option return nothing to allow asking the user + } +} + +function Get-TelemetryLevelSource { + param( + [switch]$OptOut + ) + process { + $acceptedTelemetryLevels = "None", "Basic", "Full" + + # LabConfig value has a priority + if($LabConfig.TelemetryLevel -and $LabConfig.TelemetryLevel -in $acceptedTelemetryLevels) { + return "LabConfig" + } + + # Environment variable as a fallback + if($env:WSLAB_TELEMETRY_LEVEL -and $env:WSLAB_TELEMETRY_LEVEL -in $acceptedTelemetryLevels) { + return "Environment" + } + } +} + +function Get-PcSystemType { + param( + [Parameter(Mandatory = $true)] + [int]$Id + ) + process { + $type = switch($Id) { + 1 { "Desktop" } + 2 { "Laptop" } + 3 { "Workstation" } + 4 { "Server" } + 7 { "Server" } + 5 { "Server" } + default { $Id } + } + + $type + } +} + +function New-TelemetryEvent { + param( + [Parameter(Mandatory = $true)] + [string]$Event, + $Properties, + $Metrics, + $NickName + ) + + process { + if(-not $TelemetryInstrumentationKey) { + WriteInfo "Instrumentation key is required in order to send telemetry data." + return + } + + $level = Get-TelemetryLevel + $levelSource = Get-TelemetryLevelSource + + $r = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" + $build = "$($r.CurrentMajorVersionNumber).$($r.CurrentMinorVersionNumber).$($r.CurrentBuildNumber).$($r.UBR)" + $osVersion = "$($r.ProductName) ($build)" + $hw = Get-CimInstance -ClassName Win32_ComputerSystem + $os = Get-CimInstance -ClassName Win32_OperatingSystem + $computerNameHash = $env:computername | Get-StringHash + + if(-not $NickName) { + $NickName = "?" + } + + $osType = switch ($os.ProductType) { + 1 { "Workstation" } + default { "Server" } + } + + $extraMetrics = @{} + $extraProperties = @{ + 'telemetry.level' = $level + 'telemetry.levelSource' = $levelSource + 'telemetry.nick' = $NickName + 'powershell.edition' = $PSVersionTable.PSEdition + 'powershell.version' = $PSVersionTable.PSVersion.ToString() + 'os.type' = $osType + 'hw.type' = Get-PcSystemType -Id $hw.PCSystemType + } + if($level -eq "Full") { + # OS + $extraProperties.'os.build' = $r.CurrentBuildNumber + + # RAM + $extraMetrics.'memory.total' = [Math]::Round(($hw.TotalPhysicalMemory)/1024KB, 0) + + # CPU + $extraMetrics.'cpu.logical.count' = $hw.NumberOfLogicalProcessors + $extraMetrics.'cpu.sockets.count' = $hw.NumberOfProcessors + + # Disk + $driveLetter = $ScriptRoot -Split ":" | Select-Object -First 1 + $volume = Get-Volume -DriveLetter $driveLetter + $disk = Get-VolumePhysicalDisk -Volume $driveLetter + $extraMetrics.'volume.size' = [Math]::Round($volume.Size / 1024MB) + $extraProperties.'volume.fs' = $volume.FileSystemType + $extraProperties.'disk.type' = $disk.MediaType + $extraProperties.'disk.busType' = $disk.BusType + $extraProperties.'disk.model' = $disk.FriendlyName + } + + $payload = @{ + name = "Microsoft.ApplicationInsights.Event" + time = $([System.dateTime]::UtcNow.ToString("o")) + iKey = $TelemetryInstrumentationKey + tags = @{ + "ai.application.ver" = $wslabVersion + "ai.cloud.roleInstance" = Split-Path -Path $ScriptRoot -Leaf + "ai.internal.sdkVersion" = 'wslab-telemetry:1.0.0' + "ai.session.id" = $TelemetrySessionId + "ai.device.locale" = (Get-WinsystemLocale).Name + "ai.device.id" = $computerNameHash + "ai.device.os" = "" + "ai.device.osVersion" = "" + "ai.device.oemName" = "" + "ai.device.model" = "" + } + data = @{ + baseType = "EventData" + baseData = @{ + ver = 2 + name = $Event + properties = ($Properties, $extraProperties | Merge-Hashtables) + measurements = ($Metrics, $extraMetrics | Merge-Hashtables) + } + } + } + + if($level -eq "Full") { + $payload.tags.'ai.device.os' = $r.ProductName + $payload.tags.'ai.device.osVersion' = $osVersion + $payload.tags.'ai.device.oemName' = $hw.Manufacturer + $payload.tags.'ai.device.model' = $hw.Model + if($hw.Manufacturer -eq "Lenovo") { # Lenovo sets common name of the model to SystemFamily property + $payload.tags.'ai.device.model' = $hw.SystemFamily + } + } + + $payload + } +} + +function Send-TelemetryObject { + param( + [Parameter(Mandatory = $true)] + [array]$Data + ) + + process { + $json = "{0}" -f (($Data) | ConvertTo-Json -Depth 10 -Compress) + try { + Invoke-RestMethod -Uri 'https://dc.services.visualstudio.com/v2/track' -Method Post -UseBasicParsing -Body $json -TimeoutSec 20 + } catch { + WriteInfo "`tSending telemetry failed with an error: $($_.Exception.Message)" + } + } +} + +function Send-TelemetryEvent { + param( + [Parameter(Mandatory = $true)] + [string]$Event, + + $Properties, + $Metrics, + $NickName + ) + + process { + $telemetryEvent = New-TelemetryEvent -Event $Event -Properties $Properties -Metrics $Metrics -NickName $NickName + Send-TelemetryObject -Data $telemetryEvent + } +} + +function Send-TelemetryEvents { + param( + [Parameter(Mandatory = $true)] + [array]$Events + ) + + process { + Send-TelemetryObject -Data $Events + } +} + +function Read-TelemetryLevel { + process { + # Ask user for consent + WriteInfoHighlighted "`nLab telemetry" + WriteInfo "By providing a telemetry information you will help us to improve WSLab scripts. There are two levels of a telemetry information and we are not collecting any personally identifiable information (PII)." + WriteInfo "Details about telemetry levels and the content of telemetry messages can be found in documentation https://aka.ms/wslab/telemetry" + WriteInfo "Available telemetry levels are:" + WriteInfo " * None -- No information will be sent" + WriteInfo " * Basic -- Lab info will be sent (e.g. script execution time, number of VMs)" + WriteInfo " * Full -- More details about the host machine and deployed VMs (e.g. guest OS)" + WriteInfo "Would you be OK with providing an information about your WSLab usage?" + WriteInfo "`nTip: You can also configure telemetry settings explicitly in LabConfig.ps1 file or by setting an environmental variable and suppress this prompt." + + $options = [System.Management.Automation.Host.ChoiceDescription[]] @( + <# 0 #> New-Object System.Management.Automation.Host.ChoiceDescription "&None", "No information will be sent" + <# 1 #> New-Object System.Management.Automation.Host.ChoiceDescription "&Basic", "Lab info will be sent (e.g. script execution time, number of VMs)" + <# 2 #> New-Object System.Management.Automation.Host.ChoiceDescription "&Full", "More details about the host machine and deployed VMs (e.g. guest OS)" + ) + $response = $host.UI.PromptForChoice("WSLab telemetry level", "Please choose a telemetry level for this WSLab instance. For more details please see WSLab documentation.", $options, 1 <#default option#>) + + $telemetryLevel = $null + switch($response) { + 0 { + $telemetryLevel = 'None' + WriteInfo "`nNo telemetry information will be sent." + } + 1 { + $telemetryLevel = 'Basic' + WriteInfo "`nTelemetry has been set to Basic level, thank you for your valuable feedback." + } + 2 { + $telemetryLevel = 'Full' + WriteInfo "`nTelemetry has been set to Full level, thank you for your valuable feedback." + } + } + + $telemetryLevel + } +} + +# Instance values +$ScriptRoot = $PSScriptRoot +$wslabVersion = "dev" +$TelemetryEnabledLevels = "Basic", "Full" +$TelemetryInstrumentationKey = "9ebf64de-01f8-4f60-9942-079262e3f6e0" +$TelemetrySessionId = $ScriptRoot + $env:COMPUTERNAME + ((Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Cryptography).MachineGuid) | Get-StringHash +#endregion diff --git a/Scripts/1_Prereq.ps1 b/Scripts/1_Prereq.ps1 index 3dd2aa60..c716f4de 100644 --- a/Scripts/1_Prereq.ps1 +++ b/Scripts/1_Prereq.ps1 @@ -13,50 +13,34 @@ If (-not $isAdmin) { } # Skipping 10 lines because if running when all prereqs met, statusbar covers powershell output -1..10 |% { Write-Host ""} +1..10 | ForEach-Object { Write-Host "" } #region Functions - -function WriteInfo($message){ - Write-Host $message - } - -function WriteInfoHighlighted($message){ - Write-Host $message -ForegroundColor Cyan -} - -function WriteSuccess($message){ - Write-Host $message -ForegroundColor Green -} - -function WriteError($message){ - Write-Host $message -ForegroundColor Red -} - -function WriteErrorAndExit($message){ - Write-Host $message -ForegroundColor Red - Write-Host "Press enter to continue ..." - Stop-Transcript - Read-Host | Out-Null - Exit -} +. .\0_Shared.ps1 # [!build-include-inline] function Get-WindowsBuildNumber { $os = Get-CimInstance -ClassName Win32_OperatingSystem return [int]($os.BuildNumber) } - #endregion -#region Initializtion +#region Initialization + # grab Time and start Transcript - Start-Transcript -Path "$PSScriptRoot\Prereq.log" - $StartDateTime = get-date + Start-Transcript -Path "$ScriptRoot\Prereq.log" + $StartDateTime = Get-Date WriteInfo "Script started at $StartDateTime" + WriteInfo "`nWSLab Version $wslabVersion" #Load LabConfig.... - . "$PSScriptRoot\LabConfig.ps1" + . "$ScriptRoot\LabConfig.ps1" + +# Telemetry Event + if((Get-TelemetryLevel) -in $TelemetryEnabledLevels) { + WriteInfo "Telemetry is set to $(Get-TelemetryLevel) level from $(Get-TelemetryLevelSource)" + Send-TelemetryEvent -Event "Prereq.Start" -NickName $LabConfig.TelemetryNickName | Out-Null + } #define some variables if it does not exist in labconfig If (!$LabConfig.DomainNetbiosName){ @@ -249,6 +233,15 @@ If ( Test-Path -Path "$PSScriptRoot\Temp\Convert-WindowsImage.ps1" ) { #endregion +# Telemetry Event +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 WriteInfo "Script finished at $(Get-date) and took $(((get-date) - $StartDateTime).TotalMinutes) Minutes" Stop-Transcript diff --git a/Scripts/2_CreateParentDisks.ps1 b/Scripts/2_CreateParentDisks.ps1 index df78766e..8eb5bf43 100644 --- a/Scripts/2_CreateParentDisks.ps1 +++ b/Scripts/2_CreateParentDisks.ps1 @@ -13,32 +13,7 @@ If (-not $isAdmin) { } #region Functions - - function WriteInfo($message){ - Write-Host $message - } - - function WriteInfoHighlighted($message){ - Write-Host $message -ForegroundColor Cyan - } - - function WriteSuccess($message) - { - Write-Host $message -ForegroundColor Green - } - - function WriteError($message) - { - Write-Host $message -ForegroundColor Red - } - - function WriteErrorAndExit($message){ - Write-Host $message -ForegroundColor Red - Write-Host "Press enter to continue ..." - Stop-Transcript - Read-Host | Out-Null - Exit - } +. .\0_Shared.ps1 # [!build-include-inline] #Create Unattend for VHD Function CreateUnattendFileVHD{ @@ -120,12 +95,27 @@ If (-not $isAdmin) { #region Initialization #Start Log Start-Transcript -Path "$PSScriptRoot\CreateParentDisks.log" - $StartDateTime = get-date + $StartDateTime = Get-Date WriteInfo "Script started at $StartDateTime" + WriteInfo "`nWSLab Version $wslabVersion" #Load LabConfig.... . "$PSScriptRoot\LabConfig.ps1" + # Telemetry + if(-not (Get-TelemetryLevel)) { + $telemetryLevel = Read-TelemetryLevel + $LabConfig.TelemetryLevel = $telemetryLevel + $promptShown = $true + } + + if((Get-TelemetryLevel) -in $TelemetryEnabledLevels) { + if(-not $promptShown) { + WriteInfo "Telemetry is set to $(Get-TelemetryLevel) level from $(Get-TelemetryLevelSource)" + } + Send-TelemetryEvent -Event "CreateParentDisks.Start" -NickName $LabConfig.TelemetryNickName | Out-Null + } + #create variables if not already in LabConfig If (!$LabConfig.DomainNetbiosName){ $LabConfig.DomainNetbiosName="Corp" @@ -161,7 +151,7 @@ If (-not $isAdmin) { $DCName='DC' #Grab TimeZone - $TimeZone=(Get-TimeZone).id + $TimeZone = (Get-TimeZone).id #Grab Installation type $WindowsInstallationType=Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\' -Name InstallationType @@ -253,6 +243,7 @@ If (-not $isAdmin) { if ($PSScriptRoot -like "c:\ClusterStorage*"){ WriteSuccess "`t Volume Cluster Shared Volume. Mountdir will be $env:Temp\WSLAbMountdir" $mountdir="$env:Temp\WSLAbMountdir" + $VolumeFileSystem="CSVFS" }else{ $mountdir="$PSScriptRoot\Temp\MountDir" $VolumeFileSystem=(Get-Volume -DriveLetter $driveletter).FileSystemType @@ -317,6 +308,9 @@ If (-not $isAdmin) { $ISOServer | Dismount-DiskImage WriteErrorAndExit "Please provide Windows Server 2016 and newer. Exitting." } + #Check ISO Language + $imageInfo=(Get-WindowsImage -ImagePath "$($ServerMediaDriveLetter):\sources\install.wim" -Index 4) + $OSLanguage=$imageInfo.Languages | Select-Object -First 1 #Grab packages #grab server packages @@ -331,8 +325,8 @@ If (-not $isAdmin) { WriteInfoHighlighted "Please select Windows Server Updates (*.msu). Click Cancel if you don't want any." [reflection.assembly]::loadwithpartialname("System.Windows.Forms") $msupackages = New-Object System.Windows.Forms.OpenFileDialog -Property @{ -     Multiselect = $true; - Title="Please select Windows Server Updates (*.msu). Click Cancel if you don't want any." + 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 (*.*)|*.*" If($msupackages.ShowDialog() -eq "OK"){ @@ -353,11 +347,13 @@ If (-not $isAdmin) { if ($BuildNumber -eq 14393){ #Windows Server 2016 $ServerVHDs += @{ + Kind = "Full" Edition="4" VHDName="Win2016_G2.vhdx" Size=60GB } $ServerVHDs += @{ + Kind = "Core" Edition="3" VHDName="Win2016Core_G2.vhdx" Size=30GB @@ -373,17 +369,20 @@ If (-not $isAdmin) { }elseif ($BuildNumber -eq 17763){ #Windows Server 2019 $ServerVHDs += @{ + Kind = "Full" Edition="4" VHDName="Win2019_G2.vhdx" Size=60GB } $ServerVHDs += @{ + Kind = "Core" Edition="3" VHDName="Win2019Core_G2.vhdx" Size=30GB } }elseif ($BuildNumber -ge 17744 -and $SAC){ $ServerVHDs += @{ + Kind = "Core" Edition="2" VHDName="WinSrvInsiderCore_$BuildNumber.vhdx" Size=30GB @@ -395,11 +394,13 @@ If (-not $isAdmin) { }elseif ($BuildNumber -ge 17744){ #Windows Sever Insider $ServerVHDs += @{ + Kind = "Full" Edition="4" VHDName="WinSrvInsider_$BuildNumber.vhdx" Size=60GB } $ServerVHDs += @{ + Kind = "Core" Edition="3" VHDName="WinSrvInsiderCore_$BuildNumber.vhdx" Size=30GB @@ -440,7 +441,14 @@ If (-not $isAdmin) { #Create Servers Parent VHDs WriteInfoHighlighted "Creating Server Parent disk(s)" + $vhdStatusInfo = @{} foreach ($ServerVHD in $ServerVHDs){ + $vhdStatus = @{ + Kind = $ServerVHD.Kind + Name = $ServerVHD.VHDName + AlreadyExists = $false + BuildStartDate = Get-Date + } if ($serverVHD.Edition -notlike "*nano"){ if (!(Test-Path "$PSScriptRoot\ParentDisks\$($ServerVHD.VHDName)")){ WriteInfo "`t Creating Server Parent $($ServerVHD.VHDName)" @@ -463,6 +471,7 @@ If (-not $isAdmin) { Convert-WindowsImage -SourcePath "$($ServerMediaDriveLetter):\sources\install.wim" -Edition $serverVHD.Edition -VHDPath "$PSScriptRoot\ParentDisks\$($ServerVHD.VHDName)" -SizeBytes $serverVHD.Size -VHDFormat VHDX -DiskLayout UEFI } }else{ + $vhdStatus.AlreadyExists = $true WriteSuccess "`t Server Parent $($ServerVHD.VHDName) found, skipping creation" } } @@ -484,9 +493,18 @@ If (-not $isAdmin) { WriteSuccess "`t Server Parent $($ServerVHD.VHDName) found, skipping creation" } } + $vhdStatus.BuildEndDate = Get-Date + + $vhdStatusInfo[$vhdStatus.Kind] = $vhdStatus } #create Tools VHDX from .\Temp\ToolsVHD + $toolsVhdStatus = @{ + Kind = "Tools" + Name = "tools.vhdx" + AlreadyExists = $false + BuildStartDate = Get-Date + } if (!(Test-Path "$PSScriptRoot\ParentDisks\tools.vhdx")){ WriteInfoHighlighted "Creating Tools.vhdx" $toolsVHD=New-VHD -Path "$PSScriptRoot\ParentDisks\tools.vhdx" -SizeBytes 30GB -Dynamic @@ -510,15 +528,21 @@ If (-not $isAdmin) { } Dismount-VHD $vhddisk.Number + + $toolsVhdStatus.BuildEndDate = Get-Date }else{ + $toolsVhdStatus.AlreadyExists = $true WriteSuccess "`t Tools.vhdx found in Parent Disks, skipping creation" - $toolsVHD=Get-VHD -Path "$PSScriptRoot\ParentDisks\tools.vhdx" + $toolsVHD = Get-VHD -Path "$PSScriptRoot\ParentDisks\tools.vhdx" } + + $vhdStatusInfo[$toolsVhdStatus.Kind] = $toolsVhdStatus #endregion #region Hydrate DC if (-not $DCFilesExists){ WriteInfoHighlighted "Starting DC Hydration" + $dcHydrationStartTime = Get-Date $vhdpath="$PSScriptRoot\LAB\$DCName\Virtual Hard Disks\$DCName.vhdx" $VMPath="$PSScriptRoot\LAB\" @@ -1009,6 +1033,8 @@ If (-not $isAdmin) { if (($LabConfig.InstallSCVMM -eq "Yes") -or ($LabConfig.InstallSCVMM -eq "SQL") -or ($LabConfig.InstallSCVMM -eq "ADK") -or ($LabConfig.InstallSCVMM -eq "Prereqs")){ $DC | Get-VMHardDiskDrive | Where-Object path -eq $toolsVHD.Path | Remove-VMHardDiskDrive } + + $dcHydrationEndTime = Get-Date } #endregion @@ -1051,6 +1077,7 @@ If (-not $isAdmin) { WriteInfoHighlighted "Do you want to cleanup unnecessary files and folders?" WriteInfo "`t (.\Temp\ 1_Prereq.ps1 2_CreateParentDisks.ps1 and rename 3_deploy to just deploy)" If ((Read-host "`t Please type Y or N") -like "*Y"){ + $renamed = $true WriteInfo "`t `t Cleaning unnecessary items" Remove-Item -Path "$PSScriptRoot\temp" -Force -Recurse "$PSScriptRoot\Temp","$PSScriptRoot\1_Prereq.ps1","$PSScriptRoot\2_CreateParentDisks.ps1" | ForEach-Object { @@ -1060,11 +1087,71 @@ If (-not $isAdmin) { WriteInfo "`t `t `t Renaming $PSScriptRoot\3_Deploy.ps1 to Deploy.ps1" Rename-Item -Path "$PSScriptRoot\3_Deploy.ps1" -NewName "Deploy.ps1" -ErrorAction SilentlyContinue }else{ + $renamed = $false WriteInfo "`t You did not type Y, skipping cleanup" } + # Telemetry Event + if($LabConfig.TelemetryLevel -in $TelemetryEnabledLevels) { + WriteInfo "Sending telemetry info" + $metrics = @{ + 'script.duration' = ((Get-Date) - $StartDateTime).TotalSeconds + 'msu.count' = ($packages | Measure-Object).Count + 'memory.available' = [Math]::Round($MemoryAvailableMB, 0) + } + if(-not $DCFilesExists) { + $metrics['dc.duration'] = ($dcHydrationEndTime - $dcHydrationEndTime).TotalSeconds + } + + $properties = @{ + 'dc.exists' = [int]$DCFilesExists + 'dc.edition' = $LabConfig.DCEdition + 'dc.build' = $BuildNumber + 'dc.language' = $OSLanguage + 'lab.scriptsRenamed' = $renamed + 'lab.installScvmm' = $LabConfig.InstallSCVMM + 'os.windowsInstallationType' = $WindowsInstallationType + 'os.tz' = $TimeZone + } + $events = @() + + # First for parent disks + foreach($key in $vhdStatusInfo.Keys) { + $status = $vhdStatusInfo[$key] + $buildDuration = 0 + if(-not $status.AlreadyExists) { + $buildDuration = ($status.BuildEndDate - $status.BuildStartDate).TotalSeconds + } + $key = $key.ToLower() + + $properties["vhd.$($key).exists"] = [int]$status.AlreadyExists + $properties["vhd.$($key).name"] = $status.Name + if($buildDuration -gt 0) { + $metrics["vhd.$($key).duration"] = $buildDuration + } + + if($status.AlreadyExists) { + continue # verbose events are interesting only when creating a new vhds + } + + $vhdMetrics = @{ + 'vhd.duration' = $buildDuration + } + $vhdProperties = @{ + 'vhd.name' = $status.Name + 'vhd.kind' = $status.Kind + } + $events += New-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 + + Send-TelemetryEvents -Events $events | Out-Null + } + Stop-Transcript WriteSuccess "Job Done. Press enter to continue..." Read-Host | Out-Null -#endregion \ No newline at end of file +#endregion diff --git a/Scripts/3_Deploy.ps1 b/Scripts/3_Deploy.ps1 index d189b156..ac609e05 100644 --- a/Scripts/3_Deploy.ps1 +++ b/Scripts/3_Deploy.ps1 @@ -13,34 +13,7 @@ If (-not $isAdmin) { } #region Functions - - function WriteInfo($message){ - Write-Host $message - } - - function WriteInfoHighlighted($message){ - Write-Host $message -ForegroundColor Cyan - } - - function WriteSuccess($message){ - Write-Host $message -ForegroundColor Green - } - - function WriteWarning($message) { - Write-Host $message -ForegroundColor Yellow - } - - function WriteError($message){ - Write-Host $message -ForegroundColor Red - } - - function WriteErrorAndExit($message){ - Write-Host $message -ForegroundColor Red - Write-Host "Press enter to continue ..." - Stop-Transcript - Read-Host | Out-Null - Exit - } +. .\0_Shared.ps1 # [!build-include-inline] Function CreateUnattendFileBlob{ #Create Unattend (parameter is Blob) @@ -672,6 +645,12 @@ If (-not $isAdmin) { WriteInfoHighlighted "`t Adding Virtual Hard Disk $($VHD.Path)" $VMTemp | Add-VMHardDiskDrive -Path $vhd.Path } + + # return info + @{ + OSDiskPath = $vhdpath + VM = $VMTemp + } } #endregion @@ -679,13 +658,28 @@ If (-not $isAdmin) { Start-Transcript -Path "$PSScriptRoot\Deploy.log" - $StartDateTime = get-date + $StartDateTime = Get-Date WriteInfoHighlighted "Script started at $StartDateTime" + WriteInfo "`nWSLab Version $wslabVersion" ##Load LabConfig.... . "$PSScriptRoot\LabConfig.ps1" + # Telemetry + if(-not (Get-TelemetryLevel)) { + $telemetryLevel = Read-TelemetryLevel + $LabConfig.TelemetryLevel = $telemetryLevel + $promptShown = $true + } + + if((Get-TelemetryLevel) -in $TelemetryEnabledLevels) { + if(-not $promptShown) { + WriteInfo "Telemetry is set to $(Get-TelemetryLevel) level from $(Get-TelemetryLevelSource)" + } + Send-TelemetryEvent -Event "Deploy.Start" -NickName $LabConfig.TelemetryNickName | Out-Null + } + #endregion #region Set variables @@ -860,6 +854,7 @@ If (-not $isAdmin) { if ($PSScriptRoot -like "c:\ClusterStorage*"){ WriteSuccess "`t Volume Cluster Shared Volume. Mountdir will be $env:Temp\WSLAbMountdir" $mountdir="$env:Temp\WSLAbMountDir" + $VolumeFileSystem="CSVFS" }else{ $mountdir="$PSScriptRoot\Temp\MountDir" $VolumeFileSystem=(Get-Volume -DriveLetter $driveletter).FileSystemType @@ -961,9 +956,10 @@ If (-not $isAdmin) { #Testing if lab already exists. WriteInfoHighlighted "Checking if lab already exists." + $LABExists=$false if ($SwitchNameExists){ if ((Get-vm -Name ($labconfig.prefix+"DC") -ErrorAction SilentlyContinue) -ne $null){ - $LABExists=$True + $LABExists=$true WriteInfo "`t Lab already exists. If labconfig contains additional VMs, they will be added." }else{ WriteInfo "`t Lab does not exist, will be created" @@ -1293,6 +1289,7 @@ If (-not $isAdmin) { #endregion #region Provision VMs + $vmDeploymentEvents = @() #DSC config for LCM (in case Pull configuration is specified) WriteInfoHighlighted "Creating DSC config to configure DC as pull server" @@ -1344,8 +1341,11 @@ If (-not $isAdmin) { #process $labconfig.VMs and create VMs (skip if machine already exists) WriteInfoHighlighted 'Processing $LabConfig.VMs, creating VMs' + $provisionedVMsCount = 0 foreach ($VMConfig in $LABConfig.VMs.GetEnumerator()){ if (!(Get-VM -Name "$($labconfig.prefix)$($VMConfig.vmname)" -ErrorAction SilentlyContinue)){ + $vmProvisioningStartTime = Get-Date + # Ensure that Configuration is set and use Simple as default if(-not $VMConfig.configuration) { $VMConfig.configuration = "Simple" @@ -1375,7 +1375,7 @@ If (-not $isAdmin) { $SharedHDDs=Get-VHD -Path "$LABfolder\VMs\SharedHDD-$VMSet-*.VHDS" -ErrorAction SilentlyContinue } #Build VM - BuildVM -VMConfig $VMConfig -LabConfig $labconfig -LabFolder $LABfolder + $createdVm = BuildVM -VMConfig $VMConfig -LabConfig $labconfig -LabFolder $LABfolder #Compose VMName $VMname=$Labconfig.Prefix+$VMConfig.VMName #Add disks @@ -1394,13 +1394,13 @@ If (-not $isAdmin) { #create VM with Simple configuration if ($VMConfig.configuration -eq 'Simple'){ - BuildVM -VMConfig $($VMConfig) -LabConfig $labconfig -LabFolder $LABfolder + $createdVm = BuildVM -VMConfig $($VMConfig) -LabConfig $labconfig -LabFolder $LABfolder } #create VM with S2D configuration if ($VMConfig.configuration -eq 'S2D'){ #build VM - BuildVM -VMConfig $VMConfig -LabConfig $labconfig -LabFolder $LABfolder + $createdVm = BuildVM -VMConfig $VMConfig -LabConfig $labconfig -LabFolder $LABfolder #compose VM name $VMname=$Labconfig.Prefix+$VMConfig.VMName @@ -1441,7 +1441,7 @@ If (-not $isAdmin) { $ReplicaLog=Get-VHD -Path "$LABfolder\VMs\ReplicaLog-$VMSet.VHDS" } #build VM - BuildVM -VMConfig $VMConfig -LabConfig $labconfig -LabFolder $LABfolder + $createdVm = BuildVM -VMConfig $VMConfig -LabConfig $labconfig -LabFolder $LABfolder #Add disks $VMname=$Labconfig.Prefix+$VMConfig.VMName @@ -1459,6 +1459,29 @@ If (-not $isAdmin) { WriteInfo "`t`t $filename size $($_.size /1GB)GB added to $VMname" } } + + # Telemetry Report + if((Get-TelemetryLevel) -in $TelemetryEnabledLevels) { + $properties = @{ + 'vm.configuration' = $VMConfig.Configuration + 'vm.unattend' = $VMConfig.Unattend + } + if(Test-Path -Path $createdVm.OSDiskPath) { + $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 + } + + $metrics = @{ + 'vm.deploymentDuration' = ((Get-Date) - $vmProvisioningStartTime).TotalSeconds + } + $vmInfo = New-TelemetryEvent -Event "Deploy.VM" -Properties $properties -Metrics $metrics -NickName $LabConfig.TelemetryNickName + $vmDeploymentEvents += $vmInfo + } + + $provisionedVMsCount += 1 } } @@ -1478,7 +1501,8 @@ If (-not $isAdmin) { Set-VMNetworkAdapter -VMName "$($labconfig.Prefix)*" -MacAddressSpoofing On -AllowTeaming On #list VMs - Get-VM | Where-Object name -like "$($labconfig.Prefix)*" | ForEach-Object { WriteSuccess "Machine $($_.VMName) provisioned" } + $AllVMs = Get-VM | Where-Object name -like "$($labconfig.Prefix)*" + $AllVMs | ForEach-Object { WriteSuccess "Machine $($_.VMName) provisioned" } #configure HostResourceProtection on all VM CPUs WriteInfo "`t Configuring EnableHostResourceProtection on all VM processors" @@ -1499,8 +1523,28 @@ If (-not $isAdmin) { WriteInfo "`t Enabling VMNics device naming" Get-VM -VMName "$($labconfig.Prefix)*" | Where-Object Generation -eq 2 | Set-VMNetworkAdapter -DeviceNaming On + # Telemetry Event + if((Get-TelemetryLevel) -in $TelemetryEnabledLevels) { + WriteInfo "`t Sending telemetry info" + $metrics = @{ + 'script.duration' = [Math]::Round(((Get-Date) - $StartDateTime).TotalSeconds, 2) + 'memory.available' = [Math]::Round($MemoryAvailableMB, 0) + 'lab.vmsCount.active' = ($AllVMs | Measure-Object).Count # how many VMs are running + 'lab.vmsCount.provisioned' = $provisionedVMsCount # how many VMs were created by this script run + } + $properties = @{ + 'lab.timezone' = $TimeZone + 'lab.internet' = [bool]$LabConfig.Internet + 'lab.isncrementalDeployment' = $LABExists + } + $telemetryEvent = New-TelemetryEvent -Event "Deploy.End" -Metrics $metrics -Properties $properties -NickName $LabConfig.TelemetryNickName + $vmDeploymentEvents += $telemetryEvent + + Send-TelemetryEvents -Events $vmDeploymentEvents | Out-Null + } + #write how much it took to deploy - WriteInfo "Script finished at $(Get-date) and took $(((get-date) - $StartDateTime).TotalMinutes) Minutes" + WriteInfo "Script finished at $(Get-Date) and took $(((Get-Date) - $StartDateTime).TotalMinutes) Minutes" Stop-Transcript diff --git a/Scripts/Cleanup.ps1 b/Scripts/Cleanup.ps1 index 7019707b..36688172 100644 --- a/Scripts/Cleanup.ps1 +++ b/Scripts/Cleanup.ps1 @@ -13,32 +13,12 @@ If (-not $isAdmin) { } #region Functions - function WriteInfo($message){ - Write-Host $message - } - - function WriteInfoHighlighted($message){ - Write-Host $message -ForegroundColor Cyan - } - - function WriteSuccess($message){ - Write-Host $message -ForegroundColor Green - } - - function WriteError($message){ - Write-Host $message -ForegroundColor Red - } - - function WriteErrorAndExit($message){ - Write-Host $message -ForegroundColor Red - Write-Host "Press enter to continue ..." - $exit=Read-Host - Exit - } - +. .\0_Shared.ps1 # [!build-include-inline] #endregion #region Do some clenaup + #Start + $StartDateTime = Get-Date #load LabConfig . "$PSScriptRoot\LabConfig.ps1" @@ -48,10 +28,10 @@ If (-not $isAdmin) { } #grab all VMs, switches and DC - $VMs=get-vm -Name "$($LabConfig.Prefix)*" | Where-Object Name -ne "$($LabConfig.Prefix)DC" -ErrorAction SilentlyContinue | Sort-Object -Property Name + $VMs=Get-VM -Name "$($LabConfig.Prefix)*" | Where-Object Name -ne "$($LabConfig.Prefix)DC" -ErrorAction SilentlyContinue | Sort-Object -Property Name $vSwitch=Get-VMSwitch "$($labconfig.prefix)$($LabConfig.SwitchName)" -ErrorAction SilentlyContinue $extvSwitch=Get-VMSwitch "$($labconfig.prefix)$($LabConfig.SwitchName)-External" -ErrorAction SilentlyContinue - $DC=get-vm "$($LabConfig.Prefix)DC" -ErrorAction SilentlyContinue + $DC=Get-VM "$($LabConfig.Prefix)DC" -ErrorAction SilentlyContinue #List VMs, Switches and DC If ($VMs){ @@ -134,10 +114,20 @@ If (-not $isAdmin) { } #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\" + $zipfile = "$PSScriptRoot\LAB\DC\Virtual Machines.zip" + $zipoutput = "$PSScriptRoot\LAB\DC\" Microsoft.PowerShell.Archive\Expand-Archive -Path $zipfile -DestinationPath $zipoutput -Force + # Telemetry + if((Get-TelemetryLevel) -in $TelemetryEnabledLevels) { + WriteInfo "Telemetry is set to $(Get-TelemetryLevel) level from $(Get-TelemetryLevelSource)" + $metrics = @{ + 'script.duration' = ((Get-Date) - $StartDateTime).TotalSeconds + 'lab.removed.count' = ($VMs | Measure-Object).Count + } + Send-TelemetryEvent -Event "Cleanup" -Metrics $metrics -NickName $LabConfig.TelemetryNickName | Out-Null + } + #finishing WriteSuccess "Job Done! Press enter to continue ..." $exit=Read-Host diff --git a/Scripts/LabConfig.ps1 b/Scripts/LabConfig.ps1 index 4f7986ce..5a6667a4 100644 --- a/Scripts/LabConfig.ps1 +++ b/Scripts/LabConfig.ps1 @@ -1,12 +1,12 @@ #basic config for Windows Server 2019, that creates VMs for S2D Hyperconverged scenario https://github.com/Microsoft/WSLab/tree/master/Scenarios/S2D%20Hyperconverged $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'WSLab-' ; DCEdition='4'; Internet=$true ; AdditionalNetworksConfig=@(); VMs=@()} -#Windows Server 2019 +# Windows Server 2019 1..4 | ForEach-Object {$VMNames="S2D"; $LABConfig.VMs += @{ VMName = "$VMNames$_" ; Configuration = 'S2D' ; ParentVHD = 'Win2019Core_G2.vhdx'; SSDNumber = 0; SSDSize=800GB ; HDDNumber = 12; HDDSize= 4TB ; MemoryStartupBytes= 512MB }} -#Or Windows Server 2016 +# Or Windows Server 2016 #1..4 | ForEach-Object {$VMNames="S2D"; $LABConfig.VMs += @{ VMName = "$VMNames$_" ; Configuration = 'S2D' ; ParentVHD = 'Win2016Core_G2.vhdx'; SSDNumber = 0; SSDSize=800GB ; HDDNumber = 12; HDDSize= 4TB ; MemoryStartupBytes= 512MB }} -#Or Windows Server Insider (17744 DC will not finish building. To finish build, just login to DC instead of waiting forever) -#1..4 | ForEach-Object {$VMNames="S2D"; $LABConfig.VMs += @{ VMName = "$VMNames$_" ; Configuration = 'S2D' ; ParentVHD = 'WinSrvInsiderCore_17744.vhdx'; SSDNumber = 0; SSDSize=800GB ; HDDNumber = 12; HDDSize= 4TB ; MemoryStartupBytes= 512MB }} +# Or Azure Stack HCI +#1..4 | ForEach-Object {$VMNames="S2D"; $LABConfig.VMs += @{ VMName = "$VMNames$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI20H2_G2.vhdx'; SSDNumber = 0; SSDSize=800GB ; HDDNumber = 12; HDDSize= 4TB ; MemoryStartupBytes= 512MB }} ### HELP ### @@ -35,8 +35,10 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'W 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 - DCVMVersion="9.0" # (Optional) Latest is used if nothing is specified. Make sure you use values like "8.0","8.3","9.0" + 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, WSLab users will be able to see their own stats AdditionalNetworksConfig=@(); # Just empty array for config below VMs=@(); # Just empty array for config below } @@ -170,8 +172,17 @@ $LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'W If True, then Guest Services integration component will be enabled on all VMs. This allows simple file copy from host to guests. DCVMVersion - Example: DCVMVersion="8.0" + Example: DCVMVersion="8.0" (optional) If set, version for DC will be used. It is useful if you want to keep DC older to be able to use it on previous versions of OS. + + TelemetryLevel (optional) + Example: TelemetryLevel="Full" + If set, scripts will not prompt for telemetry. Can be "None","Basic","Full" + For more info see https://aka.ms/wslab/telemetry + + TelemetryNickname (optional) + Example: TelemetryNickname="Jaromirk" + If configured, telemetry will be sent with NickName to correlate data to specified NickName. So when leaderboards will be published, WSLab users will be able to see their own stats #> #endregion diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 00000000..df08ce5c --- /dev/null +++ b/build.ps1 @@ -0,0 +1,42 @@ +param( + [Parameter(Mandatory = $true)] + [string]$Version +) + +$baseDir = ".\Scripts\" +[array]$ignoredFiles = "0_Shared.ps1" + +$releaseDirectory = New-Item -ItemType "Directory" -Path ".\" -Name "Output" +$files = Get-ChildItem -Path $baseDir +foreach($file in $files) { + if($file.Name -in $ignoredFiles) { + continue + } + $content = Get-Content -Path $file.FullName + $output = $content | ForEach-Object { + $line = $_ + + # inline include + if($line -match "^\s*\.\s+([^#]+)#\s\[!build-include-inline\]") { + $includeFile = $Matches[1] + if($includeFile.StartsWith(".\")) { + $includeFile = $includeFile.Substring(2) + } + $includeFile = Join-Path -Path $baseDir -ChildPath $includeFile + if(-not (Test-Path -Path $includeFile)) { + throw "Unable to include requested script ($includeFile)" + } + $line = Get-Content -Path $includeFile + } + + # special variable populated with current version + if($line -match '^\s*\$wslabVersion') { + $line = $line -replace '\$wslabVersion\s*=\s*"[^"]*"', "`$wslabVersion = `"$Version`"" + } + + $line + } + $outFile = Join-Path -Path $releaseDirectory -ChildPath $file.Name + Set-Content -Path $outFile -Value $output +} +Compress-Archive -Path "$($releaseDirectory.FullName)\*" -DestinationPath Release.zip -CompressionLevel Optimal