#Requires -Version 5.1 <# .SYNOPSIS Returns a Windows PowerShell 5.1 guest script; does not contact Azure or execute it. .DESCRIPTION The returned string includes the backend private key and must remain in private run files. Execute it only on the disposable workshop VM through Azure VM Run Command. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $BackendPfxBase64, [Parameter(Mandatory = $true)] [string] $RootCerBase64, [Parameter(Mandatory = $true)] [string] $PfxPassword, [Parameter(Mandatory = $true)] [string] $Marker ) Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' # Validate the embedded binary inputs before constructing the guest script. [void] [Convert]::FromBase64String($BackendPfxBase64) [void] [Convert]::FromBase64String($RootCerBase64) $passwordLiteral = $PfxPassword.Replace("'", "''") $markerBase64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Marker)) $template = @' $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' Set-StrictMode -Version 2.0 trap { Write-Output ('AWZ_BOOTSTRAP_FAILED: ' + $_.Exception.Message) exit 1 } Import-Module ServerManager $feature = Install-WindowsFeature -Name Web-Server, Web-Static-Content, Web-Default-Doc, Web-Http-Logging, Web-Filtering -IncludeManagementTools if (-not $feature.Success) { throw 'IIS role installation failed.' } Import-Module WebAdministration $temporaryDirectory = Join-Path $env:ProgramData 'AzureWorkshopz-TlsBootstrap' [void] [IO.Directory]::CreateDirectory($temporaryDirectory) $pfxPath = Join-Path $temporaryDirectory 'backend.pfx' $rootPath = Join-Path $temporaryDirectory 'root-ca.cer' $pfxPassword = ConvertTo-SecureString -String '__PFX_PASSWORD__' -AsPlainText -Force try { [IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String('__BACKEND_PFX__')) [IO.File]::WriteAllBytes($rootPath, [Convert]::FromBase64String('__ROOT_CER__')) $rootCertificate = Import-Certificate -FilePath $rootPath -CertStoreLocation 'Cert:\LocalMachine\Root' $certificates = @(Import-PfxCertificate -FilePath $pfxPath -Password $pfxPassword -CertStoreLocation 'Cert:\LocalMachine\My') $certificate = $certificates | Where-Object { $_.HasPrivateKey -and $_.Subject -eq 'CN=backend.lab.test' } | Select-Object -First 1 if ($null -eq $certificate) { throw 'The backend PFX did not contain the expected private-key leaf.' } # A default, non-SNI binding always presents the same certificate. A wrong # expected hostname at the gateway therefore isolates certificate-name validation. $binding = Get-WebBinding -Name 'Default Web Site' -Protocol 'https' | Where-Object { $_.bindingInformation -eq '*:443:' } | Select-Object -First 1 if ($null -eq $binding) { New-WebBinding -Name 'Default Web Site' -Protocol 'https' -Port 443 -IPAddress '*' -SslFlags 0 } Set-WebBinding -Name 'Default Web Site' -BindingInformation '*:443:' -PropertyName 'sslFlags' -Value 0 $binding = Get-WebBinding -Name 'Default Web Site' -Protocol 'https' | Where-Object { $_.bindingInformation -eq '*:443:' } | Select-Object -First 1 $binding.AddSslCertificate($certificate.Thumbprint, 'My') $webRoot = Join-Path $env:SystemDrive 'inetpub\wwwroot' $marker = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('__MARKER_BASE64__')) $safeMarker = [Net.WebUtility]::HtmlEncode($marker) $utf8 = New-Object Text.UTF8Encoding($false) foreach ($folder in @('control', 'probe-fault', 'tls-fault', 'docs')) { $directory = Join-Path $webRoot $folder [void] [IO.Directory]::CreateDirectory($directory) $html = '
Workshop marker: ' + $safeMarker + '
This static page does not execute or reflect query-string values.
' [IO.File]::WriteAllText((Join-Path $directory 'index.html'), $html, $utf8) } [IO.File]::WriteAllText((Join-Path $webRoot 'index.html'), 'Workshop marker: ' + $safeMarker + '
', $utf8) [IO.File]::WriteAllText((Join-Path $webRoot 'health.txt'), 'healthy', $utf8) $firewallRule = Get-NetFirewallRule -Name 'AzureWorkshopz-IIS-HTTPS' -ErrorAction SilentlyContinue if ($null -eq $firewallRule) { New-NetFirewallRule -Name 'AzureWorkshopz-IIS-HTTPS' -DisplayName 'Azure Workshopz IIS HTTPS' -Direction Inbound -Action Allow -Protocol TCP -LocalPort 443 | Out-Null } else { Set-NetFirewallRule -Name 'AzureWorkshopz-IIS-HTTPS' -Enabled True -Action Allow | Out-Null } Start-Service W3SVC Start-WebSite -Name 'Default Web Site' [pscustomobject] @{ status = 'ready' marker = $marker backend_hostname = 'backend.lab.test' backend_leaf_thumbprint = $certificate.Thumbprint root_thumbprint = $rootCertificate.Thumbprint https_binding = '*:443:' health_path = '/health.txt' powershell_version = $PSVersionTable.PSVersion.ToString() } | ConvertTo-Json -Compress } finally { # Remove imported private-key transport files, retaining only certificate-store keys. if (Test-Path -LiteralPath $pfxPath) { Remove-Item -LiteralPath $pfxPath -Force } if (Test-Path -LiteralPath $rootPath) { Remove-Item -LiteralPath $rootPath -Force } if ($null -ne $pfxPassword) { $pfxPassword.Dispose() } } '@ $template.Replace('__PFX_PASSWORD__', $passwordLiteral).Replace('__BACKEND_PFX__', $BackendPfxBase64).Replace('__ROOT_CER__', $RootCerBase64).Replace('__MARKER_BASE64__', $markerBase64)