stig/tools/scale-deployment.ps1 (422 lines of code) (raw):

#Requires -Module @{ ModuleName = 'Az.Resources'; ModuleVersion = '3.5.0' } <# .SYNOPSIS This script enables scaled STIG VM deployment using ATO Tool Kit artifacts. .DESCRIPTION This script enables scaled STIG VM deployment using ATO Tool Kit artifacts. .PARAMETER VmName Name of the virtual machine. .PARAMETER VmNamePrefix Prefix to be used with VmName. .PARAMETER VmNameSuffixDelimiter Delimiter between VmName and VmNameSuffixStartingNumber. .PARAMETER VmNameSuffixStartingNumber The starting number for the first VM in the deployment. .PARAMETER Count Number of unique VMs or VM Availability Sets to deploy. .PARAMETER VmSize Specifies the size for the virtual machine. .PARAMETER OsVersion Linux or Windows OS Version. .PARAMETER ResourceGroupName Specifies the name of the deployment resource group. .PARAMETER TemplateUri Specifies the URI of a custom template file. .PARAMETER Location Location for all resources. .PARAMETER AdminUserName Username for the Virtual Machine. .PARAMETER VirtualNetworkNewOrExisting Is the Virtual Network new or existing for the Virtual Machine. .PARAMETER VmVirtualNetwork Virtual Network for the Virtual Machine. .PARAMETER VirtualNetworkResourceGroupName Name of the resource group for the existing virtual network. .PARAMETER AddressPrefix Address prefix of the virtual network. .PARAMETER SubnetPrefix Subnet prefix of the virtual network. .PARAMETER SubnetName Subnet name for the Virtual Machine. .PARAMETER AuthenticationType Type of authentication to use on the Virtual Machine, used with Linux deployments, non-applicable for Windows. .PARAMETER ArtifactsLocation SAS Token to access the storage location containing artifacts. .PARAMETER DiagnosticStorageResourceId Diagnostic Storage account resource id. .PARAMETER LogAnalyticsWorkspaceId Log Analytics workspace resource id. .PARAMETER OsDiskEncryptionSetResourceId OS Disk Encryption Set resource id. .PARAMETER ApplicationSecurityGroupResourceId Application Security Group resource id. .PARAMETER OsDiskStorageType Azure managed disks types to support your workload or scenario. .PARAMETER CustomData Pass a script, configuration file, or other data into the virtual machine while it is being provisioned. The data will be saved on the VM in a known location. .PARAMETER AvailabilityOptions Determines if an AvailabilitySet is deployed ('availabilitySet' AvailabilitySet is created), ('default' no AvailabilitySet) .PARAMETER AvailabilitySetNameSuffix AvailabilitySetName Suffix to be used with scaled deployment. .PARAMETER InstanceCount Number of VMs to deploy per Availability Set. .PARAMETER FaultDomains Azure fault domains .PARAMETER UpdateDomains Azure update domains. .PARAMETER LogsRetentionInDays Log retention in days. .PARAMETER EnableHybridBenefitServerLicense Enable Azure Hybrid Benefit to use your on-premises Windows Server licenses and reduce cost. .PARAMETER EnableMultisessionClientLicense Windows10 Enterprise Multi-session .PARAMETER AutoInstallDependencies Boolean value to indicate an online or offline environment. .PARAMETER ArtifactsLocationSasToken SAS Token to access the storage location containing artifacts. .PARAMETER AdminPasswordOrKey SSH Key or password for the Linux Virtual Machine, password only for Windows deployments. .PARAMETER TimeInSecondsBetweenJobs Time between New-AzResourceGroupDeployment jobs to ensure sufficient time for pid deployment to succeed. .PARAMETER LinuxTemplateUri Specifies the URI of the Linux template file. .PARAMETER WindowsTemplateUri Specifies the URI of the Windows template file. .PARAMETER LinuxArtifactsLocation Specifies the URI of the Linux artifacts location. .PARAMETER WindowsArtifactsLocation Specifies the URI of the Windows artifacts location. .PARAMETER DataFilePath ATO Scale deployment data file (.psd1) .EXAMPLE $artifactLocationParams = .\publish-to-blob.ps1 -ResourceGroupName deploymentRG -StorageAccountName deploymentSA -ContainerName artifacts -MetadataPassthru $dataFilePath = 'C:\data\vmDeploymentData.psd1 $securePassword = Read-Host -Prompt 'adminUserName password' -AsSecureString .\scale-deployment.ps1 @artifactLocationParams -DataFilePath $dataFilePath -AdminPasswordOrKey $securePassword This example uses the publish-to-blob.ps1 script to copy deployment artifact files to a storage account, then deploy the solution based on data from the data file. #> [CmdletBinding(DefaultParameterSetName = 'Default')] Param ( [Parameter(Mandatory = $true, ParameterSetName = 'Default')] [ValidateScript({$_ -notmatch '[^\w-]'})] [string] $VmName, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [ValidateScript({$_ -notmatch '[^\w-]'})] [string] $VmNamePrefix = $null, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [ValidateScript({$_ -notmatch '[^\w-]'})] [string] $VmNameSuffixDelimiter = '-', [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [int] $VmNameSuffixStartingNumber = 1, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [int] $Count, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $VmSize, [Parameter(Mandatory = $true, ParameterSetName = 'Default')] [ValidateSet( "CentOS79", "CentOS78", "CentOS77", "CentOS76", "CentOS75", "CentOS74", "RHEL84", "RHEL83", "RHEL82", "RHEL81", "RHEL80", "RHEL79", "RHEL78", "RHEL77", "RHEL75", "RHEL74", "RHEL73", "RHEL72", "Ubuntu1804", "Ubuntu1804-DataScience", "2019-Datacenter", "2016-Datacenter", "19h2-ent", IgnoreCase = $false )] [string] $OsVersion, [Parameter(Mandatory = $true, ParameterSetName = 'Default')] [string] $ResourceGroupName, [Parameter(Mandatory = $true, ParameterSetName = 'Default')] [ValidateScript({[uri]::IsWellFormedUriString($_, 1)})] [string] $TemplateUri, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $Location, [Parameter(Mandatory = $true, ParameterSetName = 'Default')] [string] $AdminUserName, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [ValidateSet( "new", "existing", IgnoreCase = $false )] [string] $VirtualNetworkNewOrExisting, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $VmVirtualNetwork, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $VirtualNetworkResourceGroupName, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $AddressPrefix, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $SubnetPrefix, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $SubnetName, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $AuthenticationType, [Parameter(Mandatory = $true, ParameterSetName = 'Default')] [ValidateScript({[uri]::IsWellFormedUriString($_, 1)})] [string] $ArtifactsLocation, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $DiagnosticStorageResourceId, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $LogAnalyticsWorkspaceId, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $OsDiskEncryptionSetResourceId, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $ApplicationSecurityGroupResourceId, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [ValidateSet( "Premium_LRS", "Standard_LRS", "StandardSSD_LRS", IgnoreCase = $false )] [string] $OsDiskStorageType, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $CustomData, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $AvailabilityOptions, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [string] $AvailabilitySetNameSuffix, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [ValidateRange(1, 5)] [int] $InstanceCount, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [ValidateRange(1, 3)] [int] $FaultDomains, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [ValidateRange(1, 5)] [int] $UpdateDomains, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [ValidateRange(0, 365)] [int] $LogsRetentionInDays, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [switch] $EnableHybridBenefitServerLicense, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [switch] $EnableMultisessionClientLicense, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [switch] $AutoInstallDependencies, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [Parameter(Mandatory = $false, ParameterSetName = 'DataFilePath')] [SecureString] $ArtifactsLocationSasToken, [Parameter(Mandatory = $true, ParameterSetName = 'Default')] [Parameter(Mandatory = $true, ParameterSetName = 'DataFilePath')] [SecureString] $AdminPasswordOrKey, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [Parameter(Mandatory = $false, ParameterSetName = 'DataFilePath')] [ValidateRange(10, 120)] [int] $TimeInSecondsBetweenJobs = 10, [Parameter(Mandatory = $false, ParameterSetName = 'DataFilePath')] [ValidateScript({[uri]::IsWellFormedUriString($_, 1)})] [string] $LinuxTemplateUri, [Parameter(Mandatory = $false, ParameterSetName = 'DataFilePath')] [ValidateScript({[uri]::IsWellFormedUriString($_, 1)})] [string] $WindowsTemplateUri, [Parameter(Mandatory = $false, ParameterSetName = 'DataFilePath')] [ValidateScript({[uri]::IsWellFormedUriString($_, 1)})] [string] $LinuxArtifactsLocation, [Parameter(Mandatory = $false, ParameterSetName = 'DataFilePath')] [ValidateScript({[uri]::IsWellFormedUriString($_, 1)})] [string] $WindowsArtifactsLocation, [Parameter(Mandatory = $false, ParameterSetName = 'DataFilePath')] [ValidateScript({Test-Path -Path $_})] [string] $DataFilePath ) if ($PSCmdlet.ParameterSetName -eq 'Default') { # removing non-template params from $PSBoundParams, in order to splat New-AzResourceGroupDeployment $newAzResourceGroupDeploymentParams = $PSBoundParameters [void] $newAzResourceGroupDeploymentParams.Remove('VmName') [void] $newAzResourceGroupDeploymentParams.Remove('VmNamePrefix') [void] $newAzResourceGroupDeploymentParams.Remove('VmNameSuffixDelimiter') [void] $newAzResourceGroupDeploymentParams.Remove('VmNameSuffixStartingNumber') [void] $newAzResourceGroupDeploymentParams.Remove('Count') [void] $newAzResourceGroupDeploymentParams.Remove('TimeInSecondsBetweenJobs') [array] $psBoundKeys = $PSBoundParameters.Keys # adding AsJob for parallel execution $newAzResourceGroupDeploymentParams['AsJob'] = $true # updating params from friendly user defined, to template params; under-score usage switch ($psBoundKeys) { 'ArtifactsLocation' { $newAzResourceGroupDeploymentParams['_artifactsLocation'] = $ArtifactsLocation [void] $newAzResourceGroupDeploymentParams.Remove('ArtifactsLocation') } 'ArtifactsLocationSasToken' { $newAzResourceGroupDeploymentParams['_artifactsLocationSasToken'] = $ArtifactsLocationSasToken # the QueryString param is used in conjunction with TemplateUri when decrypted SAS token is used $decryptedSasToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ArtifactsLocationSasToken) ) $newAzResourceGroupDeploymentParams['QueryString'] = $decryptedSasToken [void] $newAzResourceGroupDeploymentParams.Remove('ArtifactsLocationSasToken') } } # creating VM names based on user input $vmNames = [System.Collections.Specialized.OrderedDictionary]::new() for ($i = $VmNameSuffixStartingNumber; $i -le $Count; $i++) { $assembledVmName = '{0}{1}{2}{3}' -f $VmNamePrefix, $VmName, $VmNameSuffixDelimiter, $i if ($AvailabilityOptions -eq 'availabilitySet') { $assembledAvName = '{0}{1}{2}{3}{4}' -f $VmNamePrefix, $VmName, $VmNameSuffixDelimiter, $i, $AvailabilitySetNameSuffix } else { $assembledAvName = $null } $vmNames[$assembledVmName] = $assembledAvName } # assembled VmName validation, if invalid, we're going to fail if ($($vmNames.Keys)[-1].Length -gt 64 -and $OsVersion -notmatch '^\d+h\d+-e(nt|vd)$|^\d+-Datacenter$') { throw "Linux VM Name: $($($vmNames.Keys)[-1]) exceeds 64 characters, unable to continue." } if ($($vmNames.Keys)[-1].Length -gt 15 -and $OsVersion -match '^\d+h\d+-e(nt|vd)$|^\d+-Datacenter$') { throw "Windows VM Name: $($($vmNames.Keys)[-1]) exceeds 15 characters, unable to continue." } foreach ($vmName in $vmNames.Keys) { $newAzResourceGroupDeploymentParams['Name'] = $vmName $newAzResourceGroupDeploymentParams['VmName'] = $vmName if ($newAzResourceGroupDeploymentParams.ContainsKey('AvailabilitySetNameSuffix')) { # the hash table "value" of the vmName "key" is the AvailabilitySetName $newAzResourceGroupDeploymentParams['AvailabilitySetName'] = $vmNames[$vmName] [void] $newAzResourceGroupDeploymentParams.Remove('AvailabilitySetNameSuffix') } # handling OsVersion mainTemplate parameter deltas if ($OsVersion -match '^\d+h\d+-e(nt|vd)$|^\d+-Datacenter$') { $newAzResourceGroupDeploymentParams['adminPassword'] = $newAzResourceGroupDeploymentParams['AdminPasswordOrKey'] [void] $newAzResourceGroupDeploymentParams.Remove('AdminPasswordOrKey') [void] $newAzResourceGroupDeploymentParams.Remove('AuthenticationType') [void] $newAzResourceGroupDeploymentParams.Remove('CustomData') } else { [void] $newAzResourceGroupDeploymentParams.Remove('AutoInstallDependencies') [void] $newAzResourceGroupDeploymentParams.Remove('EnableHybridBenefitServerLicense') [void] $newAzResourceGroupDeploymentParams.Remove('EnableMultisessionClientLicense') } $jobInfo = New-AzResourceGroupDeployment @newAzResourceGroupDeploymentParams $jobVerboseMessage = 'JobId: {0}; Name: {1}; For more details use: Get-Job -Id {0}; SecondsBetweenJobs: {2}' -f $jobInfo.Id, $jobInfo.Name, $TimeInSecondsBetweenJobs Write-Verbose -Message $jobVerboseMessage # sleep statement is required to provide sufficient time for pid deployment to succeed. Start-Sleep -Seconds $TimeInSecondsBetweenJobs } } else { # import PowerShell data file, structure should mimic .psd1 documented here: Write-Verbose -Message "Importing deployment data file: $DataFilePath" $dataFileStructureLink = 'https://github.com/Azure/ato-toolkit/tree/master/stig/tools' $deploymentDataFileImport = Import-PowerShellDataFile -Path $DataFilePath Write-Verbose -Message "-- Total deployments to be created: $($($deploymentDataFileImport[$deploymentDataFileImport.Keys]).Count)" # if the hash table keys are more than one, fail, since the structure is incorrect. if ($deploymentDataFileImport.Keys.Count -gt 1) { throw "Deployment Data File structure syntax issue, see the following link for more details: $dataFileStructureLink" } # removing DataFilePath based params from $PSBoundParams so that the default param set is used when the scale-deployment script is invoked [void] $PSBoundParameters.Remove('DataFilePath') [void] $PSBoundParameters.Remove('LinuxTemplateUri') [void] $PSBoundParameters.Remove('LinuxArtifactsLocation') [void] $PSBoundParameters.Remove('WindowsTemplateUri') [void] $PSBoundParameters.Remove('WindowsArtifactsLocation') # looping through all deployment hash tables from data file $scriptPath = Join-Path -Path $PSScriptRoot -ChildPath 'scale-deployment.ps1' foreach ($newAzResourceGroupDeploymentParams in $deploymentDataFileImport[$deploymentDataFileImport.Keys -as [string]]) { # if TemplateUri is not used in the data file, use Windows or Linux locations based on OsVersion if (-not $newAzResourceGroupDeploymentParams.ContainsKey('TemplateUri')) { # if the passed OsVersion is windows, use the WindowsTemplateUri param, else use linux if ($newAzResourceGroupDeploymentParams['OsVersion'] -match '^\d+h\d+-e(nt|vd)$|^\d+-Datacenter$') { $newAzResourceGroupDeploymentParams['TemplateUri'] = $WindowsTemplateUri } else { $newAzResourceGroupDeploymentParams['TemplateUri'] = $LinuxTemplateUri } } # if ArtifactsLocation is not used in the data file, use Windows or Linux locations based on OsVersion if (-not $newAzResourceGroupDeploymentParams.ContainsKey('ArtifactsLocation')) { # if the passed OsVersion is windows, use the WindowArtifactsLocation param, else use linux if ($newAzResourceGroupDeploymentParams['OsVersion'] -match '^\d+h\d+-e(nt|vd)$|^\d+-Datacenter$') { $newAzResourceGroupDeploymentParams['ArtifactsLocation'] = $WindowsArtifactsLocation } else { $newAzResourceGroupDeploymentParams['ArtifactsLocation'] = $LinuxArtifactsLocation } } # call the deployment script with data file hash table and params passed during script invocation. & $scriptPath @newAzResourceGroupDeploymentParams @PSBoundParameters } }