Files
Backup/_NDGOV_WindowsTeam/ITD.ITD-WindowsServer.Lifecycle/Public/New-ITDWindowsVmAzureStep1.ps1
T
Zack Meier 16427a9930 update
2026-05-07 15:37:07 -05:00

447 lines
19 KiB
PowerShell

<# ################
.SYNOPSIS
A short one-line action-based description, e.g. 'Tests if a function is valid'
.DESCRIPTION
A longer description of the function, its purpose, common use cases, etc.
.NOTES
Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
Specify a URI to a help page, this will show when Get-Help -Online is used.
.EXAMPLE
$NewVMWindowsAzureParams = @{
ComputerName = 'itdzmtest901.nd.gov';
CPU = 2;
MemoryGB = 8;
DiskOsGB = 128;
DiskDataGB = 0;
Subnet = '10.21.8.0/22';
OS = "Windows Server 2025 Datacenter";
Environment = 'Test';
AppName = 'ITD-POC-zmeier';
LicensingRestrictions = "No Licensing Restrictions";
}
New-ITDWindowsVmAzure @NewVMWindowsAzureParams -Credential $PrvCred -Verbose
.EXAMPLE
$NewVMWindowsAzureParams = @{
ComputerName = 'itdzmtest701.nd.gov';
ResourceGroupNameOverride = 'rg-shared-iis-tst';
AvailabilityZone = 2;
CPU = 2;
MemoryGB = 8;
DiskOsGB = 128;
DiskDataGB = 0;
Subnet = '10.21.8.0/22';
OS = 'Windows Server 2022 Datacenter';
Environment = 'Test';
AppName = 'ITD-POC-zmeier';
LicensingRestrictions = "No Licensing Restrictions";
}
New-ITDWindowsVmAzure @NewVMWindowsAzureParams -Credential $PrvCred -Verbose
#>
function New-ITDWindowsVmAzureStep1 {
[CmdletBinding()]
param (
[string]
$FQDN,
[string]
$ResourceGroupNameOverride,
[string]
$AppName,
[ValidateSet(1, 2, 3)]
[int]
$AvailabilityZone,
[Parameter(ParameterSetName = 'VmSize')]
[string]
$VmSize,
[Parameter(ParameterSetName = 'VmSizeDetermine')]
[int]
$CPU,
[Parameter(ParameterSetName = 'VmSizeDetermine')]
[int]
$MemoryGB,
[int]
$DiskOsGB,
[int]
$DiskDataGB,
[string]
$Subnet,
[string]
$OS,
[string]
$VMEnvironment,
#[string]
#$Subscription,
[string]
$LicensingRestrictions,
[PSCredential]
$Credential
)
begin {
}
process {
$FQDN = $FQDN.ToLower()
$Hostname = $FQDN.split('.')[0]
Write-Verbose -Message "Prepare Connections"
#$tenantId = '2dea0464-da51-4a88-bae2-b3db94bc0c54'
#$AppId = '60244573-7130-4026-9c6d-47de73f8ca29'
#$SecureStringPwd = '' #$Secret:AzureVMServicePrincipal #Pqt8Q~E-dDmQugcPPWdaK2t_4retS41VVVVOZbOx
#$SecureStringPwd = 'sQV8Q~sNLcrDl2RgB32gsSEsDVgdFhNMcBdPoaEX'
#$PSCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppId, ($SecureStringPwd | ConvertTo-SecureString -AsPlainText -Force)
#Connect-AzAccount -ServicePrincipal -Credential $PSCredential -Tenant $tenantId
Write-Verbose -Message "Prepare Credentials"
$RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password))
Write-Verbose -Message "Infoblox: Find DNS pre-existing record, or create one"
Clear-DnsClientCache
$Cidr = $Subnet
[Net.IpAddress]$NetworkId = $Cidr.split('/')[0]
#######
####### Remove 10.10.10.10 references when DNS sync is fixed
#######
[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -Server 10.10.10.10 -ErrorAction SilentlyContinue).IPAddress
$SubnetMaskInt = $CIDR.split('/')[1]
$Int64 = ([convert]::ToInt64(('1' * $SubnetMaskInt + '0' * (32 - $SubnetMaskInt)), 2))
[Net.IPAddress]$SubnetMask = '{0}.{1}.{2}.{3}' -f ([math]::Truncate($Int64 / 16777216)).ToString(),
([math]::Truncate(($Int64 % 16777216) / 65536)).ToString(),
([math]::Truncate(($Int64 % 65536) / 256)).ToString(),
([math]::Truncate($Int64 % 256)).ToString()
$IPSplit = $Subnet.Split('.')
[Net.IPAddress]$DefaultGateway = ($IPSplit[0] + '.' + $IPSplit[1] + '.' + $IPSplit[2] + '.' + (($CIDR.split('/')[0].split('.')[-1] -as [int]) + 1) )
If ($null -ne $IpAddress) {
If (($IpAddress.Address -band $SubnetMask.Address) -eq ($NetworkId.Address -band $SubnetMask.Address)) {
Write-Warning "DNS record already exists, CIDR Block match"
}
Else {
Write-Error "DNS record already exists, and does not match CIDR Block"
Break
}
}
Else {
Write-Verbose -Message "Pre-existing IP address not found, creating new DNS record."
New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred
Start-Sleep -Seconds 5
Write-Verbose -Message ("FQDN is " + $FQDN)
#######
####### Remove 10.10.10.10 references when DNS sync is fixed
#######
#[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction Stop -Server 10.10.10.10).IPAddress
[Net.IPAddress]$IpAddress = (Get-ITDIbDNSRecord -Hostname $FQDN -Credential $RadiusCred).IPv4Address
If ((Test-NetConnection -ComputerName $IpAddress.IPAddressToString).PingSucceeded) {
Write-Error "IP Address already in use." -ErrorAction Stop
}
}
Write-Verbose -Message "Passwordstate: If local administrator password does not exist in vault, create it."
If ($FQDN -like "itdcnd*") {
$PasswordStateList = "Peoplesoft Share PW"
}
Else {
$PasswordStateList = "CSRC"
}
$GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential
If ($null -eq $GuestVMLocalCredential) {
Write-Verbose -Message "Passwordstate: Local admin password record does not exist, creating new credentials"
$GuestVMLocalCredential = New-ITDPassword -Title $FQDN -UserName itdadmin -Description 'Local Administrator' -PasswordList $PasswordStateList -Credential $Credential
}
Else {
Write-Verbose -Message "Passwordstate: Local admin password record already exists, use those credentials"
}
$GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
$GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
Write-Verbose -Message "Determine environment"
switch ($VMEnvironment) {
{ $_ -eq 'Test' -or $_ -eq 'Development' } {
Write-Verbose -Message "Environment is Test or Development"
$EnvShortString = 'tst'
}
'Production' {
Write-Verbose -Message "Environment is Production"
$EnvShortString = 'prd'
}
}
Write-Verbose -Message "Determine subscription, via Subnet"
$AllSubscriptions = Get-AzSubscription
$AllAzVirtualNetworks = ForEach ($Subscription in $AllSubscriptions) {
Set-AzContext -SubscriptionObject $Subscription | Out-Null
Get-AzVirtualNetwork | ForEach-Object {
[PSCustomObject]@{
Subscription = $Subscription.Name;
VirtualNetwork = $_
}
}
}
$Subscription = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).Subscription
$VirtualNetwork = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).VirtualNetwork
$VirtualNetworkSubnet = $VirtualNetwork.Subnets | Where-Object { $_.AddressPrefix -match $Subnet }
$VNetName = $VirtualNetwork.Name
$VNetSubnet = $VirtualNetworkSubnet.Name
Write-Verbose -Message "OS is $OS"
switch ($OS) {
{ $_ -eq "Windows Server 2019 Datacenter" -or $_ -eq "Windows Server 2019" } {
Write-Verbose -Message "OS is Windows Server 2019"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsServer"
$Offer = "WindowsServer"
$sku = "2019-Datacenter"
$SecurityType = ''
}
{$_ -eq "Windows Server 2022 Datacenter" -or $_ -eq "Windows Server 2022" } {
Write-Verbose -Message "OS is Windows Server 2022"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsServer"
$Offer = "windowsserver2022"
$sku = "2022-datacenter"
$SecurityType = ''
}
{ $_ -eq "Windows Server 2025 Datacenter" -or $_ -eq "Windows Server 2025" } {
Write-Verbose -Message "OS is Windows Server 2025"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsServer"
$Offer = "WindowsServer"
$sku = "2025-datacenter"
$SecurityType = ''
}
"Windows 11 24H2" {
Write-Verbose -Message "OS is Windows 11 24H2"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsDesktop"
$Offer = "Windows-11"
$sku = "win11-24h2-ent"
$SecurityType = ''
}
Default { Write-Error "Invalid operating system" -ErrorAction Stop }
}
# finalize VM location and size
$location = "centralus"
switch ($PSCmdLet.ParameterSetName) {
"VmSize" {
Write-Verbose -Message "ParameterSet is VmSize"
$VMSize = Get-AzVMSize -Location centralus | Where-Object { $_.Name -eq $VmSize }
}
"VmSizeDetermine" {
Write-Verbose -Message "ParameterSet is VmSizeDetermine"
$VMSizeFilter1 = @(
"Standard_D2ds_v5",
"Standard_D4ds_v5",
"Standard_D8ds_v5",
"Standard_D16ds_v5",
"Standard_D32ds_v5",
"Standard_E2ds_v5",
"Standard_E4ds_v5",
"Standard_E8ds_v5",
"Standard_E16ds_v5",
"Standard_E20ds_v5",
"Standard_E32ds_v5",
"Standard_F2s_v2",
"Standard_F4s_v2",
"Standard_F8s_v2",
"Standard_F16s_v2",
"Standard_F32s_v2"
)
$VMSize = Get-AzVMSize -Location centralus | `
Where-Object { $VMSizeFilter1 -contains $_.Name } | `
Where-Object { $_.NumberOfCores -ge $CPU -and $_.MemoryInMB -ge ($Memory * 1024) } | `
Where-Object Name -NotMatch "_Promo" | `
Sort-Object NumberOfCores, MemoryInMB | `
Select-Object -First 1
}
}
Write-Verbose -Message "Determine ResourceGroupName, and create Resource Group if needed"
If ($PSBoundParameters.ContainsKey('ResourceGroupNameOverride')) {
# use the name in the variable
Write-Verbose -Message "ResourceGroupName parameter found, is $ResourceGroupNameOverride"
$ResourceGroupName = $ResourceGroupNameOverride
}
Else {
Write-Verbose -Message "ResourceGroupName parameter not found, determine now"
# if name is not given, determine the name
$VMOwner = $AppName.split('-')[0].ToLower()
$VMFunction = (($AppName -replace "$VMOwner-") -replace "-").ToLower() -replace " "
$ResourceGroupName = "rg-$VMOwner-$VMFunction-$EnvShortString"
}
Write-Verbose -Message "ResourceGroupName is $ResourceGroupName"
Write-Verbose -Message "Change to selected subscription and validate resource group exists"
Set-AzContext $Subscription | Out-Null
$ResGroupExist = $null
$ResGroupExist = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue
If (@($ResGroupExist).count -gt 1) {
Write-Error "Multiple Resource Groups matched" -ErrorAction Stop
}
If (@($ResGroupExist).count -lt 1) {
Write-Warning "No matching resource group found, creating it now"
New-AzResourceGroup -Name $ResourceGroupName -Location $Location -Tag @{ApplicationName = $AppName }
}
If (@($ResGroupExist).count -eq 1) {
Write-Verbose -Message "Exactly one matching resource group found"
}
$VMName = "vm-$HostName-$EnvShortString"
$VMIPConfigName = "ipconfig-$HostName-$EnvShortString"
$VMNicName = "nic-$HostName-$EnvShortString"
$VMObjectName = "vm-$HostName-$EnvShortString"
$VMOsDiskName = "vm-$HostName-os-$EnvShortString"
Write-Verbose -Message "Verifying Applicable Inputs are Lowercase"
$VMIPConfigName = $VMIPConfigName.ToLower()
$VMNicName = $VMNicName.ToLower()
$VMObjectName = $VMObjectName.ToLower()
$VMOsDiskName = $VMOSDiskName.ToLower()
Write-Verbose -Message "Creating IPConfig and NIC"
$IPConfig = New-AzNetworkInterfaceIpConfig -Name $VMIPConfigName -PrivateIpAddress $IPAddress.IPAddressToString -PrivateIpAddressVersion IPv4 -Subnet $VirtualNetworkSubnet
$VMNIC = New-AzNetworkInterface -IpConfigurationName $IPConfig -Location $Location -Name $VMNICName -ResourceGroupName $ResourceGroupName -Subnet $VirtualNetworkSubnet -Force
$VMNIC.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
$VMNIC.IpConfigurations[0].PrivateIpAddress = $IPAddress.IPAddressToString
Set-AzNetworkInterface -NetworkInterface $VMNIC
Write-Verbose -Message "Build VM Config"
$vmConfigParams = @{
VMName = $VMObjectName
VMSize = $VMSize.Name
}
If ($PSBoundParameters.ContainsKey('AvailabilityZone')) {
Write-Verbose -Message "AvailabilityZone parameter found, adding to vmConfigParams"
$vmConfigParams += @{
Zone = $AvailabilityZone
}
}
$vmConfig = New-AzVMConfig @vmConfigParams
# Security Profile
switch ($SecurityType) {
'TrustedLaunch' {
Write-Verbose -Message "Security Profile is TrustedLaunch"
$vmConfig = Set-AzVMSecurityProfile -VM $vmConfig -SecurityType "TrustedLaunch"
}
'' {
Write-Verbose -Message "Security Profile is empty"
}
Default {
Write-Verbose -Message "Security Profile is null"
}
}
$vmConfig | Set-AzVMOSDisk -Name $VMOSDiskName -CreateOption FromImage
If ($VMOS -eq "Windows") {
$vmConfig | Set-AzVMOperatingSystem -Windows -ComputerName $VMName -Credential $GuestVMLocalCredential
$vmConfig | Set-AzVMSourceImage -PublisherName $Publisher -Offer $Offer -Skus $Sku -Version latest
$vmConfig.OSProfile.ComputerName = $HostName
}
If ($VMOS -eq "Linux") {
$vmConfig | Set-AzVMOperatingSystem -Linux -ComputerName $VMFQDN -Credential $GuestVMLocalCredential
Switch ($VMSubscription) {
"npd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/76297098-764c-43de-8525-c9fda1b237be/resourceGroups/rg-infra-templates-tst-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-103" }
"infra01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/e53aa0c7-824d-40a2-b420-4ab77b1051d2/resourceGroups/rg-infra-templates-prd-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-403" }
"prd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/437b2bfa-850e-4464-b6c2-38a68cda7c69/resourceGroups/rg-infra-templates-prd-002/providers/Microsoft.Compute/images/vm-rhel74template-prd-003" }
}
}
$vmConfig | Add-AzVMNetworkInterface -Id $VMNIC.ID
Set-AzVMBootDiagnostic -VM $vmConfig -Enable -ResourceGroupName $ResourceGroupName
Write-Verbose "Creating VM"
New-AzVM -VM $vmConfig -ResourceGroupName $ResourceGroupName -Location $Location -DisableBginfoExtension -LicenseType "Windows_Server" #-AsJob
Write-Verbose -Message "New-AzVM completed, waiting 60 seconds before checking for completion"
Start-Sleep -Seconds 60
$VM = Get-AzVM -Name $VMObjectName -ResourceGroupName $ResourceGroupName
If ($PSBoundParameters.ContainsKey("DiskDataGB")) {
$ExistingDisks = @($VM.StorageProfile.DataDisks | Select-Object *, @{n = 'ItdId'; e = { [int]($_.Name -replace "vm-$hostname-app-$VMEnvironment-") } })
$NewDiskItdIdInt = ($ExistingDisks | Sort-Object ItdId -Descending | Select-Object -First 1).ItdId + 1
$NewDiskItdIdStr = $NewDiskItdIdInt.ToString("000")
$NewDiskName = "vm-$Hostname-app-$EnvShortString-$NewDiskItdIdStr" #vm-itduc4p1-app-tst-001
$LunID = ($ExistingDisks | Sort-Object Lun -Descending | Select-Object -First 1).Lun + 1
$count = 0
If ($ExistingDisks) {
while ($Size -match $ExistingDisks.DiskSizeGB) {
$count++
Write-Verbose -Message "DiskDataGB: $DiskDataGB, Count: $count"
If ($count -ge 11) {
Write-Error "Disk size not available" -ErrorAction Stop
}
Else {
$Size = $Size - 5
}
}
}
Write-Verbose -Message "DiskDataGB: $DiskDataGB, Count: $count"
If ($null -ne $DiskDataGB -and $DiskDataGB -gt 0) {
$AzureRmDiskConfigParams = @{
DiskSizeGB = $DiskDataGB
Location = $Location
CreateOption = "Empty"
SkuName = "Premium_LRS"
}
If ($AvailabilityZone) {
Write-Verbose "VM is located in Availability Zone $Zone"
$AzureRmDiskConfigParams += @{Zone = $Zone }
}
#$DiskConfig = New-AzureRmDiskConfig -DiskSizeGB $Size -Location $Location -CreateOption Empty -SkuName Premium_LRS
$DiskConfig = New-AzDiskConfig @AzureRmDiskConfigParams
If (!(Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $NewDiskName -ErrorAction SilentlyContinue)) {
$NewDisk = New-AzDisk -DiskName $NewDiskName -Disk $DiskConfig -ResourceGroupName $ResourceGroupName
$VM = Add-AzVMDataDisk -Name $NewDiskName -CreateOption Attach -ManagedDiskId $NewDisk.Id -VM $VM -Lun $LunID -Caching ReadOnly
Update-AzVM -VM $VM -ResourceGroupName $ResourceGroupName -AsJob
}
}
}
Write-Verbose -Message "Add networking tag to VM" -Verbose
$Tags = @{"Network"="StandardWindows"}
New-AzTag -ResourceId $VM.Id -Tag $Tags
}
end {
}
}