157 lines
7.5 KiB
PowerShell
157 lines
7.5 KiB
PowerShell
#Requires -Version 5.1
|
|
[CmdletBinding(SupportsShouldProcess)]
|
|
param(
|
|
[Parameter(Mandatory)][string]$SubscriptionId,
|
|
[string]$ResourceGroupName = 'rg-sgu-lab',
|
|
[string]$Location = 'centralus',
|
|
[string]$DeploymentPrefix = 'sgu-lab',
|
|
[Parameter(Mandatory)][string]$AdministratorUsername,
|
|
[securestring]$AdministratorPassword,
|
|
[string]$P2sRootCertificatePath,
|
|
[bool]$DeployVpnGateway = $true,
|
|
[string]$ComputerName = 'SGU-DC01',
|
|
[string]$VmSize = 'Standard_D2s_v5',
|
|
[string]$VirtualNetworkAddressPrefix = '10.77.0.0/16',
|
|
[string]$DomainControllerSubnetPrefix = '10.77.0.0/24',
|
|
[ipaddress]$DomainControllerPrivateIp = '10.77.0.4',
|
|
[string]$GatewaySubnetPrefix = '10.77.255.0/27',
|
|
[string]$VpnClientAddressPoolPrefix = '172.30.0.0/24',
|
|
[string[]]$PublicEnrollmentSourceAddressPrefixes = @(),
|
|
[string]$AdministratorSourceAddressPrefix = '',
|
|
[string]$TemplateFile = (Join-Path $PSScriptRoot '..\infra\azure\main.bicep')
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$ProgressPreference = 'SilentlyContinue'
|
|
|
|
if (-not (Get-Command az -ErrorAction SilentlyContinue)) {
|
|
throw 'Azure CLI is required. Install it from https://aka.ms/installazurecliwindows and run az login.'
|
|
}
|
|
if (-not (Test-Path -LiteralPath $TemplateFile -PathType Leaf)) {
|
|
throw "Azure Bicep template not found: $TemplateFile"
|
|
}
|
|
if (-not $AdministratorPassword) {
|
|
$AdministratorPassword = Read-Host 'Password for the local Azure VM administrator' -AsSecureString
|
|
}
|
|
|
|
$rootCertificateData = ''
|
|
if ($DeployVpnGateway) {
|
|
if (-not $P2sRootCertificatePath -or
|
|
-not (Test-Path -LiteralPath $P2sRootCertificatePath -PathType Leaf)) {
|
|
throw 'P2sRootCertificatePath is required when DeployVpnGateway is true.'
|
|
}
|
|
$rootCertificate = [Security.Cryptography.X509Certificates.X509Certificate2]::new(
|
|
(Resolve-Path -LiteralPath $P2sRootCertificatePath).Path)
|
|
if (-not ($rootCertificate.Extensions | Where-Object {
|
|
$_.Oid -and $_.Oid.Value -eq '2.5.29.19' -and $_.Format($false) -match 'CA' })) {
|
|
throw 'P2sRootCertificatePath must contain a certificate-authority certificate.'
|
|
}
|
|
$rootCertificateData = [Convert]::ToBase64String($rootCertificate.RawData)
|
|
}
|
|
|
|
$account = & az account show --output json 2>$null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw 'Azure CLI is not signed in. Run az login, then retry.'
|
|
}
|
|
& az account set --subscription $SubscriptionId --only-show-errors
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Could not select Azure subscription $SubscriptionId."
|
|
}
|
|
|
|
$deploymentDescription = if ($DeployVpnGateway) {
|
|
'Create Azure VNet, Windows Server 2025 VM, public IP, and P2S VPN Gateway'
|
|
}
|
|
else {
|
|
'Create Azure VNet, Windows Server 2025 VM, and public IP for direct enrollment'
|
|
}
|
|
if ($PSCmdlet.ShouldProcess("$ResourceGroupName in $Location", $deploymentDescription)) {
|
|
& az group create --name $ResourceGroupName --location $Location --only-show-errors --output none
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Could not create or update resource group $ResourceGroupName."
|
|
}
|
|
|
|
$temporaryRoot = Join-Path ([IO.Path]::GetTempPath()) ("sgu-azure-" + [Guid]::NewGuid().ToString('N'))
|
|
$parametersPath = Join-Path $temporaryRoot 'parameters.json'
|
|
$passwordPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($AdministratorPassword)
|
|
try {
|
|
New-Item -ItemType Directory -Path $temporaryRoot -Force | Out-Null
|
|
$acl = Get-Acl -LiteralPath $temporaryRoot
|
|
$acl.SetAccessRuleProtection($true, $false)
|
|
$acl.AddAccessRule([Security.AccessControl.FileSystemAccessRule]::new(
|
|
[Security.Principal.WindowsIdentity]::GetCurrent().User,
|
|
[Security.AccessControl.FileSystemRights]::FullControl,
|
|
[Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit',
|
|
[Security.AccessControl.PropagationFlags]::None,
|
|
[Security.AccessControl.AccessControlType]::Allow))
|
|
Set-Acl -LiteralPath $temporaryRoot -AclObject $acl
|
|
|
|
$plainPassword = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($passwordPointer)
|
|
$parameters = [ordered]@{
|
|
'$schema' = 'https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#'
|
|
contentVersion = '1.0.0.0'
|
|
parameters = [ordered]@{
|
|
deploymentPrefix = @{ value = $DeploymentPrefix }
|
|
location = @{ value = $Location }
|
|
administratorUsername = @{ value = $AdministratorUsername }
|
|
administratorPassword = @{ value = $plainPassword }
|
|
computerName = @{ value = $ComputerName }
|
|
vmSize = @{ value = $VmSize }
|
|
virtualNetworkAddressPrefix = @{ value = $VirtualNetworkAddressPrefix }
|
|
domainControllerSubnetPrefix = @{ value = $DomainControllerSubnetPrefix }
|
|
gatewaySubnetPrefix = @{ value = $GatewaySubnetPrefix }
|
|
domainControllerPrivateIp = @{ value = $DomainControllerPrivateIp.IPAddressToString }
|
|
vpnClientAddressPoolPrefix = @{ value = $VpnClientAddressPoolPrefix }
|
|
deployVpnGateway = @{ value = $DeployVpnGateway }
|
|
p2sRootCertificateData = @{ value = $rootCertificateData }
|
|
publicEnrollmentSourceAddressPrefixes = @{ value = @($PublicEnrollmentSourceAddressPrefixes) }
|
|
administratorSourceAddressPrefix = @{ value = $AdministratorSourceAddressPrefix }
|
|
}
|
|
}
|
|
[IO.File]::WriteAllText(
|
|
$parametersPath,
|
|
($parameters | ConvertTo-Json -Depth 8),
|
|
[Text.UTF8Encoding]::new($false))
|
|
$plainPassword = $null
|
|
$parameters.parameters.administratorPassword.value = $null
|
|
|
|
$deploymentName = 'sgu-{0}' -f (Get-Date -Format 'yyyyMMdd-HHmmss')
|
|
$deploymentOutput = & az deployment group create `
|
|
--name $deploymentName `
|
|
--resource-group $ResourceGroupName `
|
|
--template-file (Resolve-Path -LiteralPath $TemplateFile).Path `
|
|
--parameters "@$parametersPath" `
|
|
--only-show-errors `
|
|
--output json
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw 'Azure deployment failed. Review the Azure CLI error above; no bootstrap credential was persisted by this script.'
|
|
}
|
|
$deployment = ($deploymentOutput -join [Environment]::NewLine) | ConvertFrom-Json
|
|
}
|
|
finally {
|
|
if ($passwordPointer -ne [IntPtr]::Zero) {
|
|
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($passwordPointer)
|
|
}
|
|
$AdministratorPassword = $null
|
|
if ($temporaryRoot -and (Test-Path -LiteralPath $temporaryRoot)) {
|
|
Remove-Item -LiteralPath $temporaryRoot -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
$values = @{}
|
|
foreach ($property in $deployment.properties.outputs.PSObject.Properties) {
|
|
$values[$property.Name] = $property.Value.value
|
|
}
|
|
[pscustomobject]@{
|
|
ResourceGroupName = $ResourceGroupName
|
|
DeploymentName = $deploymentName
|
|
DomainControllerName = $values.domainControllerName
|
|
DomainControllerPrivateIp = $values.domainControllerPrivateIp
|
|
DomainControllerPublicIp = $values.domainControllerPublicIp
|
|
VpnGatewayName = $values.vpnGatewayName
|
|
VpnClientAddressPoolPrefix = $values.vpnClientAddressPoolPrefix
|
|
DeployVpnGateway = $DeployVpnGateway
|
|
PublicEnrollmentSourceAddressPrefixes = @($PublicEnrollmentSourceAddressPrefixes)
|
|
ServerBootstrapArguments = $values.serverBootstrapArguments
|
|
}
|
|
}
|