src/cloud.psm1
function Get-CloudPlatform { <# .SYNOPSIS Determines the cloud platform .DESCRIPTION This function returns a value that is one of 'ec2', 'azure', 'gcloud' or $null. The code checks for the existence of well known agent services to make the determination. #> begin { Write-Log -message ('{0} :: begin - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; } process { $cloudPlatform = $null; try { $cloudAgentServices = @(Get-Service -Name @('AmazonSSMAgent', 'Ec2Config', 'GCEAgent', 'WindowsAzureGuestAgent', 'WindowsAzureNetAgentSvc') -ErrorAction SilentlyContinue | % { $_.Name }); if ($cloudAgentServices.Contains('AmazonSSMAgent') -or $cloudAgentServices.Contains('Ec2Config')) { $cloudPlatform = 'amazon'; } elseif ($cloudAgentServices.Contains('WindowsAzureGuestAgent') -or $cloudAgentServices.Contains('WindowsAzureNetAgentSvc')) { $cloudPlatform = 'azure'; } elseif ($cloudAgentServices.Contains('GCEAgent')) { $cloudPlatform = 'gcloud'; } } catch { Write-Log -message ('{0} :: exception: {1}' -f $($MyInvocation.MyCommand.Name), $_.Exception.Message) -severity 'warn'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: inner exception: {1}' -f $($MyInvocation.MyCommand.Name), $_.Exception.InnerException.Message) -severity 'warn'; } } return $cloudPlatform; } end { Write-Log -message ('{0} :: end - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; } } function Get-CloudBucketResource { <# .SYNOPSIS Downloads a file resource from a cloud bucket #> param ( [Parameter(Mandatory = $true)] [ValidateSet('amazon', 'azure', 'google')] [string] $platform, [Parameter(Mandatory = $true)] [string] $bucket, [Parameter(Mandatory = $true)] [string] $key, [Parameter(Mandatory = $true)] [string] $destination, [switch] $overwrite = $false, [switch] $force = $false ) begin { Write-Log -message ('{0} :: begin - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; } process { if ($force) { try { New-Item -Path ([System.IO.Path]::GetDirectoryName($destination)) -ItemType Directory -Force | Out-Null; Write-Log -message ('{0} :: destination directory created: {1} ' -f $($MyInvocation.MyCommand.Name), ([System.IO.Path]::GetDirectoryName($destination))) -severity 'debug'; } catch { Write-Log -message ('{0} :: failed to create destination directory: {1} ' -f $($MyInvocation.MyCommand.Name), ([System.IO.Path]::GetDirectoryName($destination))) -severity 'error'; } } try { if (-not (Test-Path -Path ([System.IO.Path]::GetDirectoryName($destination)) -ErrorAction SilentlyContinue)) { throw [System.IO.DirectoryNotFoundException]('destination directory path does not exist: {0}. use `-force` switch or specify a destination directory path that exists.' -f ([System.IO.Path]::GetDirectoryName($destination))); } if ((-not $overwrite) -and (Test-Path -Path $destination -ErrorAction SilentlyContinue)) { throw [System.ArgumentException]('destination file exists: {0}. use `-overwrite` switch or specify a destination file path that does not exist.' -f $destination); } switch -regex ($platform) { 'amazon' { if (-not (Get-CloudCredentialAvailability -platform $platform)) { throw ('no credentials detected for platform: {0}' -f $platform); } # https://docs.aws.amazon.com/powershell/latest/reference/items/Copy-S3Object.html Copy-S3Object -BucketName $bucket -Key $key -LocalFile $destination | Out-Null; break; } 'azure' { # https://docs.microsoft.com/en-us/powershell/module/az.storage/get-azstorageblobcontent?view=azps-1.8.0 Get-AzStorageBlobContent -Container $bucket -Blob $key -Destination $destination | Out-Null; break; } 'google' { # https://googlecloudplatform.github.io/google-cloud-powershell/#/google-cloud-storage/GcsObject/Read-GcsObject Read-GcsObject -Bucket $bucket -ObjectName $key -OutFile $destination | Out-Null; break; } default { throw [System.ArgumentException]('unsupported platform: {0}. use: amazon|azure|google' -f $platform); break; } } if (Test-Path -Path $destination -ErrorAction SilentlyContinue) { $sizeInBytes = ((Get-Item -Path $destination -ErrorAction SilentlyContinue).Length); Write-Log -message ('{0} :: {1} ({2:n2} gb) fetched from {3}/{4}/{5}' -f $($MyInvocation.MyCommand.Name), $destination, ($sizeInBytes / 1GB), $platform, $bucket, $key) -severity 'info'; } else { Write-Log -message ('{0} :: error fetching {1} from {2}/{3}/{4}' -f $($MyInvocation.MyCommand.Name), $destination, $platform, $bucket, $key) -severity 'warn'; } } catch { Write-Log -message ('{0} :: exception fetching {1} from {2}/{3}/{4}: {5}' -f $($MyInvocation.MyCommand.Name), $destination, $platform, $bucket, $key, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: inner exception fetching {1} from {2}/{3}/{4}: {5}' -f $($MyInvocation.MyCommand.Name), $destination, $platform, $bucket, $key, $_.Exception.InnerException.Message) -severity 'error'; } } } end { Write-Log -message ('{0} :: end - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; } } function Set-CloudBucketResource { <# .SYNOPSIS Uploads a local file resource to a cloud bucket #> param ( [Parameter(Mandatory = $true)] [ValidateSet('amazon', 'azure', 'google')] [string] $platform, [Parameter(Mandatory = $true)] [string] $bucket, [Parameter(Mandatory = $true)] [string] $key, [Parameter(Mandatory = $true)] [string] $source ) begin { Write-Log -message ('{0} :: begin - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; } process { try { switch -regex ($platform) { 'amazon' { if (-not (Get-CloudCredentialAvailability -platform $platform)) { throw ('no credentials detected for platform: {0}' -f $platform); } # https://docs.aws.amazon.com/powershell/latest/reference/items/Write-S3Object.html Write-S3Object -BucketName $bucket -Key $key -File $source | Out-Null; break; } 'azure' { # https://docs.microsoft.com/en-us/powershell/module/az.storage/get-azstorageblobcontent?view=azps-1.8.0 Set-AzStorageBlobContent -Container $bucket -Blob $key -File $source | Out-Null; break; } 'google' { # https://googlecloudplatform.github.io/google-cloud-powershell/#/google-cloud-storage/GcsObject/Write-GcsObject Write-GcsObject -Bucket $bucket -ObjectName $key -File $source | Out-Null; break; } default { throw [System.ArgumentException]('unsupported platform: {0}. use: amazon|azure' -f $platform); break; } } Write-Log -message ('{0} :: {1} uploaded to {2}/{3}/{4}' -f $($MyInvocation.MyCommand.Name), $source, $platform, $bucket, $key) -severity 'info'; } catch { Write-Log -message ('{0} :: exception uploading {1} to {2}/{3}/{4}: {5}' -f $($MyInvocation.MyCommand.Name), $source, $platform, $bucket, $key, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: inner exception uploading {1} to {2}/{3}/{4}: {5}' -f $($MyInvocation.MyCommand.Name), $source, $platform, $bucket, $key, $_.Exception.InnerException.Message) -severity 'error'; } } } end { Write-Log -message ('{0} :: end - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; } } function Test-CloudBucketResource { <# .SYNOPSIS Checks whether or not a cloud bucket resource exists #> param ( [Parameter(Mandatory = $true)] [ValidateSet('amazon', 'azure', 'google')] [string] $platform, [Parameter(Mandatory = $true)] [string] $bucket, [Parameter(Mandatory = $true)] [string] $key ) begin { Write-Log -message ('{0} :: begin - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace' -supressOutput:$true; } process { switch -regex ($platform) { 'amazon' { if (-not (Get-CloudCredentialAvailability -platform $platform)) { throw ('no credentials detected for platform: {0}' -f $platform); } # https://docs.aws.amazon.com/powershell/latest/reference/items/Get-S3Object.html return [bool] (@(Get-S3Object -BucketName $bucket -Key $key).Length -gt 0); break; } 'azure' { # https://docs.microsoft.com/en-us/powershell/module/az.storage/get-azstorageblob?view=azps-1.8.0 return [bool] (@(Get-AzStorageBlob -Container $bucket -Blob $key).Length -gt 0); break; } 'google' { # https://googlecloudplatform.github.io/google-cloud-powershell/#/google-cloud-storage/GcsObject/Get-GcsObject return [bool] (@(Get-GcsObject -Bucket $bucket -ObjectName $key).Length -gt 0); break; } default { throw [System.ArgumentException]('unsupported platform: {0}. use: amazon|azure' -f $platform); break; } } } end { Write-Log -message ('{0} :: end - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace' -supressOutput:$true; } } function Get-CloudCredentialAvailability { <# .SYNOPSIS Downloads a file resource from a cloud bucket #> param ( [ValidateSet('amazon', 'azure', 'google')] [string] $platform ) begin { Write-Log -message ('{0} :: begin - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; } process { switch ($platform) { 'amazon' { return ((@(Get-AWSCredential -ErrorAction SilentlyContinue).Length -gt 0) -or (@(Get-AWSCredential -ListProfileDetail -ErrorAction SilentlyContinue).Length -gt 0)); break; } 'azure' { throw [System.NotImplementedException]('this method is awaiting implementation for platform: {0}' -f $platform); break; } 'google' { throw [System.NotImplementedException]('this method is awaiting implementation for platform: {0}' -f $platform); break; } default { throw [System.ArgumentException]('unsupported platform: {0}. use: amazon-s3|azure-blob-storage|google-cloud-storage' -f $platform); break; } } } end { Write-Log -message ('{0} :: end - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; } } function New-CloudInstanceFromImageExport { <# .SYNOPSIS Instantiates a new cloud instance from an exported image #> param ( [Parameter(Mandatory = $true)] [ValidateSet('amazon', 'azure', 'google')] [string] $platform, [Parameter(Mandatory = $true)] [string] $localImagePath, [Parameter(Mandatory = $true)] [string] $targetResourceId, [Parameter(Mandatory = $true)] [string] $targetResourceGroupName, [Parameter(Mandatory = $true)] [string] $targetResourceRegion, [Parameter(Mandatory = $true)] [string] $targetInstanceName, [string] $targetInstanceMachineVariantFormat = 'Standard_F{0}s_v2', [string] $targetInstanceHyperVGeneration = $(if ($targetInstanceMachineVariantFormat.EndsWith('_v2')) { 'V2' } else { 'V1' }), [int] $targetInstanceCpuCount = 2, [int] $targetInstanceRamGb = 8, [hashtable] $targetInstanceTags = @{}, [hashtable[]] $targetInstanceDisks = @( @{ 'Variant' = 'ssd'; 'SizeInGB' = 64; 'Os' = $true }, @{ 'Variant' = 'ssd'; 'SizeInGB' = 128 }, @{ 'Variant' = 'ssd'; 'SizeInGB' = 128 } ), [Parameter(Mandatory = $true)] [string] $targetVirtualNetworkName, [Parameter(Mandatory = $true)] [string] $targetVirtualNetworkAddressPrefix, [string[]] $targetVirtualNetworkDnsServers = @('1.1.1.1', '1.0.0.1'), [Parameter(Mandatory = $true)] [string] $targetSubnetName, [Parameter(Mandatory = $true)] [string] $targetSubnetAddressPrefix, [string] $targetFirewallConfigurationName = ('{0}-{1}' -f $(if ($platform -eq 'azure') { 'nsg' } else { 'fwc' }), $targetResourceGroupName), [hashtable[]] $targetFirewallRules = @( @{ 'Name' = 'rdp-only'; 'Description' = 'allow: inbound tcp connections, for: rdp, from: anywhere, to: any host, on port: 3389'; 'Access' = 'Allow'; 'Protocol' = 'Tcp'; 'Direction' = 'Inbound'; 'Priority' = 110; 'SourceAddressPrefix' = 'Internet'; 'SourcePortRange' = '*'; 'DestinationAddressPrefix' = '*'; 'DestinationPortRange' = '3389' }, @{ 'Name' = 'ssh-only'; 'Description' = 'allow: inbound tcp connections, for: ssh, from: anywhere, to: any host, on port: 22'; 'Access' = 'Allow'; 'Protocol' = 'Tcp'; 'Direction' = 'Inbound'; 'Priority' = 120; 'SourceAddressPrefix' = 'Internet'; 'SourcePortRange' = '*'; 'DestinationAddressPrefix' = '*'; 'DestinationPortRange' = '22' } ), [Parameter(Mandatory = $false)] [switch] $disablePlatformAgent = $false, [Parameter(Mandatory = $false)] [switch] $disableBackgroundInfo = $disablePlatformAgent ) begin { Write-Log -message ('{0} :: begin - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; Write-Log -message ('{0} :: param/platform: {1}' -f $($MyInvocation.MyCommand.Name), $platform) -severity 'trace'; Write-Log -message ('{0} :: param/localImagePath: {1}' -f $($MyInvocation.MyCommand.Name), $localImagePath) -severity 'trace'; Write-Log -message ('{0} :: param/targetResourceId: {1}' -f $($MyInvocation.MyCommand.Name), $targetResourceId) -severity 'trace'; Write-Log -message ('{0} :: param/targetResourceGroupName: {1}' -f $($MyInvocation.MyCommand.Name), $targetResourceGroupName) -severity 'trace'; Write-Log -message ('{0} :: param/targetResourceRegion: {1}' -f $($MyInvocation.MyCommand.Name), $targetResourceRegion) -severity 'trace'; Write-Log -message ('{0} :: param/targetInstanceName: {1}' -f $($MyInvocation.MyCommand.Name), $targetInstanceName) -severity 'trace'; foreach ($key in $targetInstanceTags.Keys) { Write-Log -message ('{0} :: param/targetInstanceTags[{1}]: {2}' -f $($MyInvocation.MyCommand.Name), $key, $targetInstanceTags[$key]) -severity 'trace'; } Write-Log -message ('{0} :: param/targetInstanceMachineVariantFormat: {1}' -f $($MyInvocation.MyCommand.Name), $targetInstanceMachineVariantFormat) -severity 'trace'; Write-Log -message ('{0} :: param/targetInstanceCpuCount: {1}' -f $($MyInvocation.MyCommand.Name), $targetInstanceCpuCount) -severity 'trace'; Write-Log -message ('{0} :: param/targetInstanceRamGb: {1}' -f $($MyInvocation.MyCommand.Name), $targetInstanceRamGb) -severity 'trace'; for ($i = 0; $i -lt $targetInstanceDisks.Length; $i++) { foreach ($key in $targetInstanceDisks[$i].Keys) { Write-Log -message ('{0} :: param/targetInstanceDisks[{1}][{2}]: {3}' -f $($MyInvocation.MyCommand.Name), $i, $key, $targetInstanceDisks[$i][$key]) -severity 'trace'; } } Write-Log -message ('{0} :: param/targetVirtualNetworkName: {1}' -f $($MyInvocation.MyCommand.Name), $targetVirtualNetworkName) -severity 'trace'; Write-Log -message ('{0} :: param/targetVirtualNetworkAddressPrefix: {1}' -f $($MyInvocation.MyCommand.Name), $targetVirtualNetworkAddressPrefix) -severity 'trace'; if ($targetVirtualNetworkDnsServers.GetType().Name.StartsWith('List')) { for ($i = 0; $i -lt $targetVirtualNetworkDnsServers.Count; $i++) { Write-Log -message ('{0} :: param/targetVirtualNetworkDnsServers[{1}]: {2}' -f $($MyInvocation.MyCommand.Name), $i, $targetVirtualNetworkDnsServers[$i]) -severity 'trace'; } } else { Write-Log -message ('{0} :: param/targetVirtualNetworkDnsServers: {1}' -f $($MyInvocation.MyCommand.Name), $targetVirtualNetworkDnsServers) -severity 'trace'; } Write-Log -message ('{0} :: param/targetSubnetName: {1}' -f $($MyInvocation.MyCommand.Name), $targetSubnetName) -severity 'trace'; Write-Log -message ('{0} :: param/targetSubnetAddressPrefix: {1}' -f $($MyInvocation.MyCommand.Name), $targetSubnetAddressPrefix) -severity 'trace'; Write-Log -message ('{0} :: param/targetFirewallConfigurationName: {1}' -f $($MyInvocation.MyCommand.Name), $targetFirewallConfigurationName) -severity 'trace'; for ($i = 0; $i -lt $targetFirewallRules.Length; $i++) { foreach ($key in $targetFirewallRules[$i].Keys) { if ($targetFirewallRules[$i][$key].GetType().Name.StartsWith('List')) { for ($j = 0; $j -lt $targetFirewallRules[$i][$key].Count; $j++) { Write-Log -message ('{0} :: param/targetFirewallRules[{1}][{2}][{3}]: {4}' -f $($MyInvocation.MyCommand.Name), $i, $key, $j, $targetFirewallRules[$i][$key][$j]) -severity 'trace'; } } else { Write-Log -message ('{0} :: param/targetFirewallRules[{1}][{2}]: {3}' -f $($MyInvocation.MyCommand.Name), $i, $key, $targetFirewallRules[$i][$key]) -severity 'trace'; } } } } process { switch -regex ($platform) { 'amazon' { throw [System.NotImplementedException]('this method is awaiting implementation for platform: {0}' -f $platform); break; } 'azure' { switch ($targetInstanceCpuCount) { default { switch ($targetInstanceRamGb) { default { $azMachineVariant = ($targetInstanceMachineVariantFormat -f $targetInstanceCpuCount); break; } } break; } } Write-Log -message ('{0} :: var/azMachineVariant: {1}' -f $($MyInvocation.MyCommand.Name), $azMachineVariant) -severity 'trace'; # resource group try { $azResourceGroup = (Get-AzResourceGroup -Name $targetResourceGroupName -Location $targetResourceRegion); if (-not ($azResourceGroup)) { $azResourceGroup = (New-AzResourceGroup ` -Name $targetResourceGroupName ` -Location $targetResourceRegion); Write-Log -message ('{0} :: resource group create operation for resource group: {1}, in region: {2}, has status: {3}' -f $($MyInvocation.MyCommand.Name), $targetResourceGroupName, $targetResourceRegion, $azResourceGroup.ProvisioningState) -severity 'debug'; } else { Write-Log -message ('{0} :: resource group find operation for resource group: {1}, in region: {2}, suceeded' -f $($MyInvocation.MyCommand.Name), $targetResourceGroupName, $targetResourceRegion) -severity 'debug'; } } catch { Write-Log -message ('{0} :: exception detecting or creating resource group: {1}. {2}' -f $($MyInvocation.MyCommand.Name), $targetResourceGroupName, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: inner exception detecting or creating resource group: {1}. {2}' -f $($MyInvocation.MyCommand.Name), $targetResourceGroupName, $_.Exception.InnerException.Message) -severity 'error'; } exit 1; } # boot/os disk $azDiskName = ('disk-{0}' -f $targetResourceId); try { $uploadSizeInBytes = ((Get-Item -Path $localImagePath).Length); $azDiskConfig = (New-AzDiskConfig ` -SkuName 'StandardSSD_LRS' ` -OsType 'Windows' ` -UploadSizeInBytes $uploadSizeInBytes ` -Location $targetResourceRegion ` -CreateOption 'Upload' ` -HyperVGeneration $targetInstanceHyperVGeneration); Write-Log -message ('{0} :: disk config create operation for disk: {1}, with disk variant: {2}, hyper v generation: {3} and upload size: {4:n2} gb, completed' -f $($MyInvocation.MyCommand.Name), $azDiskName, 'StandardSSD_LRS', $targetInstanceHyperVGeneration, ($uploadSizeInBytes / 1GB)) -severity 'debug'; $azDisk = (New-AzDisk ` -ResourceGroupName $targetResourceGroupName ` -DiskName $azDiskName ` -Disk $azDiskConfig); Write-Log -message ('{0} :: disk create operation for disk: {1}, in resource group: {2}, has provisioning state: {3} and disk state: {4}' -f $($MyInvocation.MyCommand.Name), $azDisk.Name, $targetResourceGroupName, $azDisk.ProvisioningState, $azDisk.DiskState) -severity 'debug'; } catch { Write-Log -message ('{0} :: exception creating disk: {1}. {2}' -f $($MyInvocation.MyCommand.Name), $azDiskName, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: inner exception creating disk: {1}. {2}' -f $($MyInvocation.MyCommand.Name), $azDiskName, $_.Exception.InnerException.Message) -severity 'error'; } exit 1; } $stopwatch = [Diagnostics.Stopwatch]::StartNew(); $azDisk = (Get-AzDisk -ResourceGroupName $targetResourceGroupName -DiskName $azDiskName); while ( ($azDisk.DiskState -ne 'ReadyToUpload') -and ($stopwatch.Elapsed.TotalSeconds -lt 120) ) { Write-Log -message ('{0} :: disk: {1}, in resource group: {2}, has disk state: "{3}". awaiting disk state: "ReadyToUpload"' -f $($MyInvocation.MyCommand.Name), $azDisk.Name, $targetResourceGroupName, $azDisk.DiskState) -severity 'debug'; $azDisk = (Get-AzDisk -ResourceGroupName $targetResourceGroupName -DiskName $azDiskName); } if ($azDisk.DiskState -ne 'ReadyToUpload') { Write-Log -message ('{0} :: disk: {1}, in resource group: {2}, has disk state: "{3}". timeout reached while awaiting disk state: "ReadyToUpload"' -f $($MyInvocation.MyCommand.Name), $azDisk.Name, $targetResourceGroupName, $azDisk.DiskState) -severity 'error'; exit 1; } $stopwatch.Stop(); try { $azDiskAccessGrantOperation = (Grant-AzDiskAccess ` -ResourceGroupName $targetResourceGroupName ` -DiskName $azDisk.Name ` -DurationInSecond 86400 ` -Access 'Write'); Write-Log -message ('{0} :: grant access operation on disk: {1}, in resource group: {2}, has status: {3}' -f $($MyInvocation.MyCommand.Name), $azDisk.Name, $targetResourceGroupName, $azDiskAccessGrantOperation.Status) -severity 'debug'; } catch { Write-Log -message ('{0} :: exception granting disk write access on: {1}. {2}' -f $($MyInvocation.MyCommand.Name), $azDisk.Name, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: inner exception granting disk write access on: {1}. {2}' -f $($MyInvocation.MyCommand.Name), $azDisk.Name, $_.Exception.InnerException.Message) -severity 'error'; } exit 1; } & AzCopy.exe @('copy', $localImagePath, ($azDiskAccessGrantOperation.AccessSAS), '--blob-type', 'PageBlob'); try { $azDiskAccessRevokeOperation = (Revoke-AzDiskAccess ` -ResourceGroupName $targetResourceGroupName ` -DiskName $azDisk.Name); Write-Log -message ('{0} :: revoke access operation on disk: {1}, in resource group: {2}, has status: {3}' -f $($MyInvocation.MyCommand.Name), $azDisk.Name, $targetResourceGroupName, $azDiskAccessRevokeOperation.Status) -severity 'debug'; } catch { Write-Log -message ('{0} :: exception revoking disk write access on: {1}. {2}' -f $($MyInvocation.MyCommand.Name), $azDisk.Name, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: inner exception revoking disk write access on: {1}. {2}' -f $($MyInvocation.MyCommand.Name), $azDisk.Name, $_.Exception.InnerException.Message) -severity 'error'; } exit 1; } # network security group (firewall rules and exceptions) $azNetworkSecurityGroup = (Get-AzNetworkSecurityGroup ` -Name $targetFirewallConfigurationName ` -ResourceGroupName $targetResourceGroupName ` -ErrorAction SilentlyContinue); if (-not ($azNetworkSecurityGroup)) { $azNetworkSecurityRuleConfigs = @($targetFirewallRules | % { (New-AzNetworkSecurityRuleConfig ` -Name $_.Name ` -Description $_.Description ` -Access $_.Access ` -Protocol $_.Protocol ` -Direction $_.Direction ` -Priority $_.Priority ` -SourceAddressPrefix $_.SourceAddressPrefix ` -SourcePortRange $_.SourcePortRange ` -DestinationAddressPrefix $_.DestinationAddressPrefix ` -DestinationPortRange $_.DestinationPortRange) }); $azNetworkSecurityGroup = (New-AzNetworkSecurityGroup ` -Name $targetFirewallConfigurationName ` -ResourceGroupName $targetResourceGroupName ` -Location $targetResourceRegion ` -SecurityRules $azNetworkSecurityRuleConfigs); Write-Log -message ('{0} :: network security group create operation for network security group: {1}, in resource group: {2}, has status: {3}' -f $($MyInvocation.MyCommand.Name), $targetFirewallConfigurationName, $targetResourceGroupName, $azNetworkSecurityGroup.ProvisioningState) -severity 'debug'; } else { Write-Log -message ('{0} :: network security group find operation for network security group: {1}, in resource group: {2}, suceeded' -f $($MyInvocation.MyCommand.Name), $targetFirewallConfigurationName, $targetResourceGroupName) -severity 'debug'; } # virtual network and subnet $azVirtualNetwork = (Get-AzVirtualNetwork ` -Name $targetVirtualNetworkName ` -ResourceGroupName $targetResourceGroupName ` -ErrorAction SilentlyContinue); if (-not ($azVirtualNetwork)) { $azVirtualNetworkSubnetConfig = (New-AzVirtualNetworkSubnetConfig ` -Name $targetSubnetName ` -AddressPrefix $targetSubnetAddressPrefix ` -NetworkSecurityGroup $azNetworkSecurityGroup); $azVirtualNetwork = (New-AzVirtualNetwork ` -Name $targetVirtualNetworkName ` -ResourceGroupName $targetResourceGroupName ` -Location $targetResourceRegion ` -AddressPrefix $targetVirtualNetworkAddressPrefix ` -Subnet $azVirtualNetworkSubnetConfig ` -DnsServer $targetVirtualNetworkDnsServers); Write-Log -message ('{0} :: virtual network create operation for virtual network: {1}, in resource group: {2}, has status: {3}' -f $($MyInvocation.MyCommand.Name), $targetVirtualNetworkName, $targetResourceGroupName, $azVirtualNetwork.ProvisioningState) -severity 'debug'; } else { Write-Log -message ('{0} :: virtual network find operation for virtual network: {1}, in resource group: {2}, suceeded' -f $($MyInvocation.MyCommand.Name), $targetVirtualNetworkName, $targetResourceGroupName) -severity 'debug'; if (-not ($azVirtualNetwork.Subnets[0].NetworkSecurityGroup)) { $azVirtualNetwork = (Set-AzVirtualNetworkSubnetConfig ` -Name $targetSubnetName ` -VirtualNetwork $azVirtualNetwork ` -AddressPrefix $targetSubnetAddressPrefix ` -NetworkSecurityGroup $azNetworkSecurityGroup); $azVirtualNetwork = ($azVirtualNetwork | Set-AzVirtualNetwork); if ($azVirtualNetwork.Subnets[0].NetworkSecurityGroup) { Write-Log -message ('{0} :: virtual network update (associate network security group: {1}) operation for virtual network: {2}, in resource group: {3}, suceeded' -f $($MyInvocation.MyCommand.Name), $azVirtualNetwork.Subnets[0].NetworkSecurityGroup.Name, $targetVirtualNetworkName, $targetResourceGroupName) -severity 'debug'; } } } # public ip address $azPublicIpAddress = (New-AzPublicIpAddress ` -Name ('ip-{0}' -f $targetResourceId) ` -ResourceGroupName $targetResourceGroupName ` -Location $targetResourceRegion ` -AllocationMethod 'Dynamic'); Write-Log -message ('{0} :: public ip address create operation for public ip address: {1}, in resource group: {2}, has status: {3}' -f $($MyInvocation.MyCommand.Name), ('ip-{0}' -f $targetResourceId), $targetResourceGroupName, $azPublicIpAddress.ProvisioningState) -severity 'debug'; $azNetworkInterface = (New-AzNetworkInterface ` -Name ('ni-{0}' -f $targetResourceId) ` -ResourceGroupName $targetResourceGroupName ` -Location $targetResourceRegion ` -SubnetId $azVirtualNetwork.Subnets[0].Id ` -PublicIpAddressId $azPublicIpAddress.Id ` -NetworkSecurityGroupId $azNetworkSecurityGroup.Id); Write-Log -message ('{0} :: network interface create operation for network interface: {1}, in resource group: {2}, on subnet: {3}, with public ip: {4}, in network security group: {5}, has status: {6}' -f $($MyInvocation.MyCommand.Name), ('ni-{0}' -f $targetResourceId), $targetResourceGroupName, $azVirtualNetwork.Subnets[0].Id.Split('/')[-1], $azPublicIpAddress.Id.Split('/')[-1], $azNetworkSecurityGroup.Id.Split('/')[-1], $azNetworkInterface.ProvisioningState) -severity 'debug'; # virtual machine $azVM = (New-AzVMConfig ` -VMName $targetInstanceName ` -VMSize $azMachineVariant); Write-Log -message ('{0} :: instance config create operation for instance: {1}, with machine variant: {2}, completed' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $azMachineVariant) -severity 'debug'; # storage account $targetStorageAccountName = ('{0}cib' -f $targetResourceGroupName.Replace('rg-', '').Replace('-', '')); try { $azStorageAccount = (Get-AzStorageAccount ` -ResourceGroupName $targetResourceGroupName ` -Name $targetStorageAccountName ` -ErrorAction 'SilentlyContinue'); if ($azStorageAccount) { Write-Log -message ('{0} :: detected storage account: {1}, for resource group: {2}' -f $($MyInvocation.MyCommand.Name), $azStorageAccount.StorageAccountName, $targetResourceGroupName) -severity 'debug'; } else { $azStorageAccount = (New-AzStorageAccount ` -ResourceGroupName $targetResourceGroupName ` -AccountName $targetStorageAccountName ` -Location $targetResourceRegion ` -SkuName 'Standard_LRS'); Write-Log -message ('{0} :: created storage account: {1}, for resource group: {2}' -f $($MyInvocation.MyCommand.Name), $azStorageAccount.StorageAccountName, $targetResourceGroupName) -severity 'debug'; } } catch { Write-Log -message ('{0} :: failed to detect or create storage account: {1}, for resource group: {2}. {3}' -f $($MyInvocation.MyCommand.Name), $targetStorageAccountName, $targetResourceGroupName, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: inner exception: {1}' -f $($MyInvocation.MyCommand.Name), $_.Exception.InnerException.Message) -severity 'error'; } $targetStorageAccountName = $null; } # enable boot diagnostics if ($targetStorageAccountName) { $azVMRollback = $azVM; try { $azVM = (Set-AzVMBootDiagnostic ` -VM $azVM ` -Enable ` -ResourceGroupName $targetResourceGroupName ` -StorageAccountName $targetStorageAccountName) Write-Log -message ('{0} :: vm boot diagnostics enabled and set to storage account: {1}, in resource group: {2}' -f $($MyInvocation.MyCommand.Name), $targetStorageAccountName, $targetResourceGroupName) -severity 'debug'; } catch { Write-Log -message ('{0} :: failed to enable vm boot diagnostics and set to storage account: {1}, in resource group: {2}. {3}' -f $($MyInvocation.MyCommand.Name), $targetStorageAccountName, $targetResourceGroupName, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: inner exception: {1}' -f $($MyInvocation.MyCommand.Name), $_.Exception.InnerException.Message) -severity 'error'; } $azVM = $azVMRollback; } } $azVM = (Add-AzVMNetworkInterface ` -VM $azVM ` -Id $azNetworkInterface.Id); Write-Log -message ('{0} :: add network interface operation for instance: {1}, in resource group: {2}, completed' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $targetResourceGroupName) -severity 'debug'; for ($i = 0; $i -lt $targetInstanceDisks.Length; $i++) { switch ($targetInstanceDisks[$i].Variant) { 'hdd' { $azDiskVariant = 'Standard_LRS'; break; } 'ssd' { $azDiskVariant = 'StandardSSD_LRS'; break; } default { $azDiskVariant = 'Standard_LRS'; break; } } if ($targetInstanceDisks[$i].Os) { $azVM = (Set-AzVMOSDisk ` -VM $azVM ` -ManagedDiskId $azDisk.Id ` -StorageAccountType $azDiskVariant ` -DiskSizeInGB $targetInstanceDisks[$i].SizeInGB ` -CreateOption 'Attach' ` -Windows:$true); Write-Log -message ('{0} :: set os disk operation for instance: {1}, in resource group: {2}, for os disk: {3}, with disk variant: {4}, completed' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $targetResourceGroupName, $azDisk.Id.Split('/')[-1], $azDiskVariant) -severity 'debug'; } else { $azVM = (Add-AzVMDataDisk ` -VM $azVM ` -Name ('{0}-data-disk-{1}' -f $targetInstanceName, ($i - 1)) ` -Lun ($i - 1) ` -DiskSizeInGB $targetInstanceDisks[$i].SizeInGB ` -CreateOption 'Empty' ` -StorageAccountType $azDiskVariant); Write-Log -message ('{0} :: add data disk operation for instance: {1}, in resource group: {2}, for data disk with lun: {3}, with disk variant: {4}, completed' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $targetResourceGroupName, ($i - 1), $azDiskVariant) -severity 'debug'; } } if ($disablePlatformAgent) { try { #$azVM = (Set-AzVMOperatingSystem ` # -VM $azVM ` # -Windows ` # -ComputerName $targetInstanceName ` # -ProvisionVMAgent:$false ` # -EnableAutoUpdate:$false); $azVM.OSProfile.AllowExtensionOperations = $false; $azVM.OSProfile.WindowsConfiguration.ProvisionVMAgent = $false; Write-Log -message ('{0} :: instance os configuration operation for instance: {1}, with platform agent: {2}, in resource group: {3}, in region: {4}, completed' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $(if ($disablePlatformAgent) { 'disabled' } else { 'enabled' }), $targetResourceGroupName, $targetResourceRegion) -severity 'debug'; } catch { Write-Log -message ('{0} :: instance os configuration operation for instance: {1}, with platform agent: {2}, in resource group: {3}, in region: {4}, threw exception: {5}' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $(if ($disablePlatformAgent) { 'disabled' } else { 'enabled' }), $targetResourceGroupName, $targetResourceRegion, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: instance os configuration operation for instance: {1}, with platform agent: {2}, in resource group: {3}, in region: {4}, threw inner exception: {5}' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $(if ($disablePlatformAgent) { 'disabled' } else { 'enabled' }), $targetResourceGroupName, $targetResourceRegion, $_.Exception.InnerException.Message) -severity 'error'; } } } try { $azVM = (New-AzVM ` -ResourceGroupName $targetResourceGroupName ` -Location $targetResourceRegion ` -Tag $targetInstanceTags ` -DisableBginfoExtension:$disableBackgroundInfo ` -VM $azVM ` -ErrorAction SilentlyContinue ` -ErrorVariable 'newAzVmError'); if (($newAzVmError) -and ($newAzVmError.Exception) -and ($newAzVmError.Exception.Message)) { switch -Regex ($newAzVmError.Exception.Message) { 'has not reported status for VM agent or extensions' { Write-Log -message ('{0} :: instance create operation for instance: {1}, with machine variant: {2}, using os disk: {3}, in resource group: {4}, in region: {5}, completed without vm-agent or extension status reporting' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $azMachineVariant, $azDisk.Id.Split('/')[-1], $targetResourceGroupName, $targetResourceRegion) -severity 'debug'; } default { Write-Log -message ('{0} :: instance create operation for instance: {1}, with machine variant: {2}, using os disk: {3}, in resource group: {4}, in region: {5}, completed with error(s): {6}' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $azMachineVariant, $azDisk.Id.Split('/')[-1], $targetResourceGroupName, $targetResourceRegion, $newAzVmError.Exception.Message) -severity 'warn'; } } } else { Write-Log -message ('{0} :: instance create operation for instance: {1}, with machine variant: {2}, using os disk: {3}, in resource group: {4}, in region: {5}, completed' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $azMachineVariant, $azDisk.Id.Split('/')[-1], $targetResourceGroupName, $targetResourceRegion) -severity 'debug'; } } catch { Write-Log -message ('{0} :: instance create operation for instance: {1}, with machine variant: {2}, using os disk: {3}, in resource group: {4}, in region: {5}, threw exception: {6}' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $azMachineVariant, $azDisk.Id.Split('/')[-1], $targetResourceGroupName, $targetResourceRegion, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: instance create operation for instance: {1}, with machine variant: {2}, using os disk: {3}, in resource group: {4}, in region: {5}, threw inner exception: {6}' -f $($MyInvocation.MyCommand.Name), $targetInstanceName, $azMachineVariant, $azDisk.Id.Split('/')[-1], $targetResourceGroupName, $targetResourceRegion, $_.Exception.InnerException.Message) -severity 'error'; } } break; } 'google' { throw [System.NotImplementedException]('this method is awaiting implementation for platform: {0}' -f $platform); break; } default { throw [System.ArgumentException]('unsupported platform: {0}. use: amazon-s3|azure-blob-storage|google-cloud-storage' -f $platform); break; } } } end { Write-Log -message ('{0} :: end - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; } } function New-CloudImageFromInstance { <# .SYNOPSIS Instantiates a new cloud instance from an exported image #> param ( [Parameter(Mandatory = $true)] [ValidateSet('amazon', 'azure', 'google')] [string] $platform, [Parameter(Mandatory = $true)] [string] $resourceGroupName, [Parameter(Mandatory = $true)] [string] $region, [Parameter(Mandatory = $true)] [string] $instanceName, [Parameter(Mandatory = $true)] [string] $imageName, [hashtable] $imageTags = $null ) begin { Write-Log -message ('{0} :: begin - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; Write-Log -message ('{0} :: param/platform: {1}' -f $($MyInvocation.MyCommand.Name), $platform) -severity 'trace'; Write-Log -message ('{0} :: param/resourceGroupName: {1}' -f $($MyInvocation.MyCommand.Name), $resourceGroupName) -severity 'trace'; Write-Log -message ('{0} :: param/region: {1}' -f $($MyInvocation.MyCommand.Name), $region) -severity 'trace'; Write-Log -message ('{0} :: param/instanceName: {1}' -f $($MyInvocation.MyCommand.Name), $instanceName) -severity 'trace'; Write-Log -message ('{0} :: param/imageName: {1}' -f $($MyInvocation.MyCommand.Name), $imageName) -severity 'trace'; foreach ($key in $imageTags.Keys) { Write-Log -message ('{0} :: param/imageTags.{1}: {2}' -f $($MyInvocation.MyCommand.Name), $key, $imageTags[$key]) -severity 'trace'; } } process { switch -regex ($platform) { 'amazon' { throw [System.NotImplementedException]('this method is awaiting implementation for platform: {0}' -f $platform); break; } 'azure' { $azVmStatuses = (Get-AzVM ` -ResourceGroupName $resourceGroupName ` -Name $instanceName ` -Status).Statuses; Write-Log -message ('{0} :: instance: {1}, in resource group: {2}, has status codes: {3}' -f $($MyInvocation.MyCommand.Name), $instanceName, $resourceGroupName, ([string]::Join(', ', @($azVmStatuses | % { $_.Code })))) -severity 'debug'; # regardless of the instance current state (running, stopped, etc.), it must be explicitly powered off using Stop-AzVM or the subsequent generalize operation will fail try { $stopOperation = (Stop-AzVM ` -ResourceGroupName $resourceGroupName ` -Name $instanceName ` -Force ` -ErrorAction SilentlyContinue); Write-Log -message ('{0} :: stop operation on instance: {1}, in resource group: {2}, has status: {3}' -f $($MyInvocation.MyCommand.Name), $instanceName, $resourceGroupName, $stopOperation.Status) -severity 'debug'; } catch { Write-Log -message ('{0} :: stop operation on instance: {1}, in resource group: {2}, failed: {3}' -f $($MyInvocation.MyCommand.Name), $instanceName, $resourceGroupName, $_.Exception.Message) -severity 'error'; } $generalizeOperation = (Set-AzVm ` -ResourceGroupName $resourceGroupName ` -Name $instanceName ` -Generalized); Write-Log -message ('{0} :: generalize operation on instance: {1}, in resource group: {2}, has status: {3}' -f $($MyInvocation.MyCommand.Name), $instanceName, $resourceGroupName, $generalizeOperation.Status) -severity 'debug'; $azVM = (Get-AzVM ` -ResourceGroupName $resourceGroupName ` -Name $instanceName); try { $azImageConfig = (New-AzImageConfig ` -Location $region ` -SourceVirtualMachineId $azVM.Id); Write-Log -message ('{0} :: image config creation operation from instance id: {1}, in region: {2}, completed' -f $($MyInvocation.MyCommand.Name), $instanceName, $region) -severity 'debug'; try { $azImage = (New-AzImage ` -Image $azImageConfig ` -ImageName $imageName ` -ResourceGroupName $resourceGroupName); if ($azImage) { Write-Log -message ('{0} :: image creation operation from instance: {1}, in resource group: {2}, has status: {3}, for image: {4}, in region: {5}' -f $($MyInvocation.MyCommand.Name), $instanceName, $resourceGroupName, $azImage.ProvisioningState, $azImage.Name, $azImage.Location) -severity 'debug'; if ($imageTags) { try { $azImageResource = (Set-AzResource ` -ResourceId $azImage.id ` -Tag $imageTags ` -Force); Write-Log -message ('{0} :: image tagging operation for image: {1}, completed' -f $($MyInvocation.MyCommand.Name), $imageName) -severity 'debug'; } catch { Write-Log -message ('{0} :: image tagging operation for image: {1}, in resource group: {2}, threw exception: {3}' -f $($MyInvocation.MyCommand.Name), $imageName, $resourceGroupName, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: image tagging operation for image: {1}, in resource group: {2}, threw inner exception: {3}' -f $($MyInvocation.MyCommand.Name), $imageName, $resourceGroupName, $_.Exception.InnerException.Message) -severity 'error'; } } } } } catch { Write-Log -message ('{0} :: image create operation for image: {1}, in resource group: {2}, threw exception: {3}' -f $($MyInvocation.MyCommand.Name), $imageName, $resourceGroupName, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: image create operation for image: {1}, in resource group: {2}, threw inner exception: {3}' -f $($MyInvocation.MyCommand.Name), $imageName, $resourceGroupName, $_.Exception.InnerException.Message) -severity 'error'; } throw } } catch { Write-Log -message ('{0} :: image config creation operation from instance: {1}, in region: {2}, threw exception: {3}' -f $($MyInvocation.MyCommand.Name), $instanceName, $region, $_.Exception.Message) -severity 'error'; if ($_.Exception.InnerException) { Write-Log -message ('{0} :: image config creation operation from instance: {1}, in region: {2}, threw inner exception: {3}' -f $($MyInvocation.MyCommand.Name), $instanceName, $region, $_.Exception.InnerException.Message) -severity 'error'; } throw } break; } 'google' { throw [System.NotImplementedException]('this method is awaiting implementation for platform: {0}' -f $platform); break; } default { throw [System.ArgumentException]('unsupported platform: {0}. use: amazon-s3|azure-blob-storage|google-cloud-storage' -f $platform); break; } } } end { Write-Log -message ('{0} :: end - {1:o}' -f $($MyInvocation.MyCommand.Name), (Get-Date).ToUniversalTime()) -severity 'trace'; } } |