AutomatedLabAzure.psm1
$PSDefaultParameterValues = @{ '*-Azure*:Verbose' = $false '*-Azure*:Warning' = $false 'Import-Module:Verbose' = $false } function Update-LabAzureSettings { # .ExternalHelp AutomatedLab.Help.xml if ((Get-PSCallStack).Command -contains 'Import-Lab') { $Script:lab = Get-Lab } elseif ((Get-PSCallStack).Command -contains 'Add-LabAzureSubscription') { $Script:lab = Get-LabDefinition if (-not $Script:lab) { $Script:lab = Get-Lab } } else { $Script:lab = Get-Lab -ErrorAction SilentlyContinue } if (-not $Script:lab) { $Script:lab = Get-LabDefinition } if (-not $Script:lab) { throw 'No Lab or Lab Definition available' } } function Add-LabAzureSubscription { # .ExternalHelp AutomatedLab.Help.xml param ( [string]$Path, [string]$SubscriptionName, [string]$DefaultLocationName, [string]$DefaultStorageAccountName, [string]$DefaultResourceGroupName, [switch]$PassThru ) Write-LogFunctionEntry Update-LabAzureSettings if (-not $Path) { $Path = (Get-ChildItem -Path (Get-LabSourcesLocation) -Filter '*.azurermsettings' -Recurse | Sort-Object -Property TimeWritten | Select-Object -Last 1).FullName Write-ScreenInfo -Message "No ARM profile file specified. Auto-detected and using ARM profile file '$Path'" -Type Warning } if (-not $script:lab) { throw 'No lab defined. Please call New-LabDefinition first before calling Set-LabDefaultOperatingSystem.' } if (-not (Test-Path -Path $Path)) { throw "The ARM profile file '$Path' could not be found" } #This needs to be loaded manually to import the required DLLs $minimumAzureModuleVersion = $MyInvocation.MyCommand.Module.PrivateData.MinimumAzureModuleVersion if (-not (Get-Module -Name Azure -ListAvailable | Where-Object Version -ge $minimumAzureModuleVersion)) { throw "The Azure PowerShell module version $($minimumAzureModuleVersion) or greater is not available. Please download it from 'http://azure.microsoft.com/en-us/downloads/'" } Write-ScreenInfo -Message 'Adding Azure subscription data' -Type Info -TaskStart try { $AzureRmProfile = Select-AzureRmProfile -Path $Path -ErrorAction Stop } catch { throw "The Azure Resource Manager Profile $Path could not be loaded or is outdated. $($_.Exception.Message)" } Update-LabAzureSettings if (-not $script:lab.AzureSettings) { $script:lab.AzureSettings = New-Object AutomatedLab.AzureSettings } $script:lab.AzureSettings.AzureProfilePath = $Path $script:lab.AzureSettings.SubscriptionFileContent = Get-Content -Path $Path $script:lab.AzureSettings.DefaultRoleSize = $MyInvocation.MyCommand.Module.PrivateData.DefaultAzureRoleSize # Select the subscription which is associated with this AzureRmProfile $subscriptions = (Get-AzureRmSubscription) $script:lab.AzureSettings.Subscriptions = [AutomatedLab.Azure.AzureSubscription]::Create($Subscriptions) Write-Verbose "Added $($script:lab.AzureSettings.Subscriptions.Count) subscriptions" if ($SubscriptionName -and -not ($script:lab.AzureSettings.Subscriptions | Where-Object SubscriptionName -eq $SubscriptionName)) { throw "A subscription named '$SubscriptionName' cannot be found. Make sure you specify the right subscription name or let AutomatedLab choose on by not defining a subscription name" } #select default subscription subscription if (-not $SubscriptionName) { $SubscriptionName = $AzureRmProfile.Context.Subscription.SubscriptionName } Write-ScreenInfo -Message "Using Azure Subscription '$SubscriptionName'" -Type Info $selectedSubscription = $Subscriptions | Where-Object{$_.SubscriptionName -eq $SubscriptionName} try { [void](Select-AzureRmSubscription -SubscriptionName $SubscriptionName -ErrorAction Stop) } catch { throw "Error selecting subscription $SubscriptionName. $($_.Exception.Message)" } $script:lab.AzureSettings.DefaultSubscription = [AutomatedLab.Azure.AzureSubscription]::Create($selectedSubscription) Write-Verbose "Azure subscription '$SubscriptionName' selected as default" $locations = Get-AzureRmLocation $script:lab.AzureSettings.Locations = [AutomatedLab.Azure.AzureLocation]::Create($locations) Write-Verbose "Added $($script:lab.AzureSettings.Locations.Count) locations" if (-not $DefaultLocationName) { $DefaultLocationName = Get-LabAzureLocation } try { Set-LabAzureDefaultLocation -Name $DefaultLocationName -ErrorAction Stop Write-ScreenInfo -Message "Using Azure Location '$DefaultLocationName'" -Type Info } catch { throw 'Cannot proceed without a valid location specified' } Write-ScreenInfo -Message "Trying to locate or create default resource group" # Create if no default given or default set and not existing as RG if(-not $DefaultResourceGroupName -or ($DefaultResourceGroupName -and -not (Get-AzureRmResourceGroup -Name $DefaultResourceGroupName -ErrorAction SilentlyContinue))) { # Create new lab resource group as default $rgName = $DefaultResourceGroupName if (-not $rgName) { $rgName = $script:lab.Name } $rgParams = @{ Name= $rgName Location = $DefaultLocationName Tag = @{ AutomatedLab = $script:lab.Name CreationTime = Get-Date } } $createResourceGroup = $true if (Get-AzureRmResourceGroup $rgName -ErrorAction SilentlyContinue) { $createResourceGroup = $false } if ($createResourceGroup) { $DefaultResourceGroupName = (New-AzureRmResourceGroup @rgParams -ErrorAction Stop).ResourceGroupName } else { $DefaultResourceGroupName = $rgName } Write-Verbose "Selected $DefaultResourceGroupName as default resource group" } $resourceGroups = Get-AzureRmResourceGroup $script:lab.AzureSettings.ResourceGroups = [AutomatedLab.Azure.AzureResourceGroup]::Create($resourceGroups) Write-Verbose "Added $($script:lab.AzureSettings.ResourceGroups.Count) resource groups" if (-not (Get-LabAzureDefaultResourceGroup -ErrorAction SilentlyContinue)) { New-LabAzureResourceGroup -ResourceGroupNames (Get-LabDefinition).Name -LocationName $DefaultLocationName } $storageAccounts = Get-AzureRmStorageAccount -ResourceGroupName $DefaultResourceGroupName -WarningAction SilentlyContinue foreach($storageAccount in $storageAccounts) { $alStorageAccount = [AutomatedLab.Azure.AzureRmStorageAccount]::Create($storageAccount) $alStorageAccount.StorageAccountKey = ($storageAccount | Get-AzureRmStorageAccountKey)[0].Value $script:lab.AzureSettings.StorageAccounts.Add($alStorageAccount) } Write-Verbose "Added $($script:lab.AzureSettings.StorageAccounts.Count) storage accounts" if ($global:cacheAzureRoleSizes) { Write-ScreenInfo -Message "Querying available vm sizes for Azure location '$DefaultLocationName' (using cache)" -Type Info $roleSizes = $global:cacheAzureRoleSizes | Where-Object { $_.InstanceSize -in (Get-LabAzureDefaultLocation).VirtualMachineRoleSizes } } else { Write-ScreenInfo -Message "Querying available vm sizes for Azure location '$DefaultLocationName'" -Type Info $roleSizes = Get-AzureRmVmSize -Location $DefaultLocationName $global:cacheAzureRoleSizes = $roleSizes } # Add LabSources storage New-LabAzureLabSourcesStorage $script:lab.AzureSettings.RoleSizes = [AutomatedLab.Azure.AzureRmVmSize]::Create($roleSizes) Write-Verbose "Added $($script:lab.AzureSettings.RoleSizes.Count) vm size information" $script:lab.AzureSettings.VNetConfig = (Get-AzureRmVirtualNetwork) | ConvertTo-Json Write-Verbose 'Added virtual network configuration' if ($global:cacheVmImages) { Write-ScreenInfo -Message 'Querying available operating system images (using cache)' -Type Info $vmImages = $global:cacheVmImages } else { Write-ScreenInfo -Message 'Querying available operating system images' -Type Info $vmImages = Get-AzureRmVMImagePublisher -Location $DefaultLocationName | Where-Object PublisherName -eq 'MicrosoftWindowsServer' | Get-AzureRmVMImageOffer | Get-AzureRmVMImageSku | Get-AzureRmVMImage | Group-Object -Property Skus, Offer | ForEach-Object { $_.Group | Sort-Object -Property PublishedDate -Descending | Select-Object -First 1 } $vmImages += Get-AzureRmVMImagePublisher -Location $DefaultLocationName | Where-Object PublisherName -eq 'MicrosoftSQLServer' | Get-AzureRmVMImageOffer | Get-AzureRmVMImageSku | Get-AzureRmVMImage | Where-Object Skus -eq 'Enterprise' | Group-Object -Property Skus, Offer | ForEach-Object { $_.Group | Sort-Object -Property PublishedDate -Descending | Select-Object -First 1 } $vmImages += Get-AzureRmVMImagePublisher -Location $DefaultLocationName | Where-Object PublisherName -eq 'MicrosoftVisualStudio' | Get-AzureRmVMImageOffer | Get-AzureRmVMImageSku | Get-AzureRmVMImage | Where-Object Offer -eq 'VisualStudio' | Group-Object -Property Skus, Offer | ForEach-Object { $_.Group | Sort-Object -Property PublishedDate -Descending | Select-Object -First 1 } $global:cacheVmImages = $vmImages } $script:lab.AzureSettings.VmImages = [AutomatedLab.Azure.AzureOSImage]::Create($vmImages) Write-Verbose "Added $($script:lab.AzureSettings.VmImages.Count) virtual machine images" $vms = Get-AzureRmVM -WarningAction SilentlyContinue $script:lab.AzureSettings.VirtualMachines = [AutomatedLab.Azure.AzureVirtualMachine]::Create($vms) Write-Verbose "Added $($script:lab.AzureSettings.VirtualMachines.Count) virtual machines" #$script:lab.AzureSettings.DefaultStorageAccount cannot be set when creating the definitions but is during the import process if (-not $script:lab.AzureSettings.DefaultStorageAccount) { Write-ScreenInfo -Message 'No default storage account exist. Determining storage account now' -Type Info if (-not $DefaultStorageAccountName) { $DefaultStorageAccountName = ($script:lab.AzureSettings.StorageAccounts | Where-Object StorageAccountName -like 'automatedlab????????' | Select-Object -First 1).StorageAccountName } if (-not $DefaultStorageAccountName) { Write-ScreenInfo -Message 'No storage account for AutomatedLab found. Creating a storage account now' New-LabAzureDefaultStorageAccount -LocationName $DefaultLocationName -ResourceGroupName $DefaultResourceGroupName } else { try { Set-LabAzureDefaultStorageAccount -Name $DefaultStorageAccountName -ErrorAction Stop Write-ScreenInfo -Message "Using Azure Storage Account '$DefaultStorageAccountName'" -Type Info } catch { throw 'Cannot proceed with an invalid default storage account' } } Write-Verbose "Mapping storage account '$((Get-LabAzureDefaultStorageAccount).StorageAccountName)' to resource group $DefaultResourceGroupName'" [void](Set-AzureRmCurrentStorageAccount -Name $((Get-LabAzureDefaultStorageAccount).StorageAccountName) -ResourceGroupName $DefaultResourceGroupName) } <# TODO, seems deprecated and or dangerous Add all additional Azure Services if configured $resourceGroupNames = (Get-LabMachine).AzureProperties.ResourceGroupName | Select-Object -Unique if ($resourceGroupNames) { #Rename to new-labazureresourcegroup New-LabAzureResourceGroup -ServiceName $resourceGroupNames -LocationName $lab.AzureSettings.DefaultLocation -ErrorAction Stop }#> Write-ScreenInfo -Message "Azure default resource group name will be '$($script:lab.Name)'" Write-ScreenInfo -Message "Azure data center location will be '$DefaultLocationName'" -Type Info Write-ScreenInfo -Message 'Finished adding Azure subscription data' -Type Info -TaskEnd if ($PassThru) { $script:lab.AzureSettings.Subscription } Write-LogFunctionExit } function Get-LabAzureSubscription { # .ExternalHelp AutomatedLab.Help.xml param () Write-LogFunctionEntry Update-LabAzureSettings $script:lab.AzureSettings.Subscriptions Write-LogFunctionExit } function Get-LabAzureDefaultSubscription { # .ExternalHelp AutomatedLab.Help.xml param () Write-LogFunctionEntry Update-LabAzureSettings $script:lab.AzureSettings.DefaultSubscription Write-LogFunctionExit } function Get-LabAzureLocation { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding()] param ( [string]$LocationName, [switch]$List ) Write-LogFunctionEntry #Update-LabAzureSettings Import-Module -Name Azure* $azureLocations = Get-AzureRmLocation if ($LocationName) { if ($LocationName -notin ($azureLocations.DisplayName)) { Write-Error "Invalid location. Please specify one of the following locations: ""'$($azureLocations.DisplayName -join ''', ''')" return } $azureLocations | Where-Object DisplayName -eq $LocationName } else { if ((Get-Lab -ErrorAction SilentlyContinue) -and (-not $list)) { #if lab already exists, use the location used when this was deployed to create lab stickyness return (Get-Lab).AzureSettings.DefaultLocation.Name } $urls = @{ 'North Central US' = 'speedtestnsus.blob.core.windows.net' 'Central US'='speedtestcus.blob.core.windows.net' 'West Central US'='speedtestwcus.blob.core.windows.net' 'South Central US'='speedtestscus.blob.core.windows.net' 'West US' = 'speedtestwus.blob.core.windows.net' 'West US 2' = 'speedtestwus2.blob.core.windows.net' 'East US'='speedtesteus.blob.core.windows.net' 'East US 2'='speedtesteus2.blob.core.windows.net' 'West Europe'='speedtestwe.blob.core.windows.net' 'North Europe'='speedtestne.blob.core.windows.net' 'Southeast Asia'='speedtestsea.blob.core.windows.net' 'East Asia'='speedtestea.blob.core.windows.net' 'Japan East'='speedtestjpe.blob.core.windows.net' 'Japan West'='speedtestjpw.blob.core.windows.net' 'Brazil South'='speedtestbs.blob.core.windows.net' 'Australia Southeast'='mickmel.blob.core.windows.net' 'Australia East'='micksyd.blob.core.windows.net' 'UK West'='speedtestukw.blob.core.windows.net' 'UK South'='speedtestuks.blob.core.windows.net' 'Canada Central'='speedtestcac.blob.core.windows.net' 'Canada East'='speedtestcae.blob.core.windows.net' } foreach ($location in $azureLocations) { $location | Add-Member -MemberType NoteProperty -Name 'Url' -Value ($urls."$($location.DisplayName)") $location | Add-Member -MemberType NoteProperty -Name 'Latency' -Value 9999 } $jobs = @() foreach ($location in $azureLocations) { $url = $location.Url $jobs += Start-Job -Name $location.DisplayName -ScriptBlock { $testUrl = $using:url try { (Test-Port -ComputerName $testUrl -Port 443 -Count 4 -ErrorAction Stop| Measure-Object -Property ResponseTime -Average).Average } catch { 9999 #Write-Warning "$testUrl $($_.Exception.Message)" } } } Wait-LWLabJob -Job $jobs -NoDisplay foreach ($job in $jobs) { $result = Receive-Job -Keep -Job $job ($azureLocations | Where-Object {$_.DisplayName -eq $job.Name}).Latency = $result } $jobs | Remove-Job Write-Verbose -Message 'DisplayName Latency' foreach ($location in $azureLocations) { Write-Verbose -Message "$($location.DisplayName.PadRight(20)): $($location.Latency)" } if ($List) { $azureLocations | Sort-Object -Property Latency | Format-Table DisplayName, Latency } else { $azureLocations | Sort-Object -Property Latency | Select-Object -First 1 | Select-Object -ExpandProperty DisplayName } } Write-LogFunctionExit } function Get-LabAzureDefaultLocation { # .ExternalHelp AutomatedLab.Help.xml [cmdletbinding()] param () Write-LogFunctionEntry Update-LabAzureSettings if (-not $Script:lab.AzureSettings.DefaultLocation) { Write-Error 'The default location is not defined. Use Set-LabAzureDefaultLocation to define it.' return } $Script:lab.AzureSettings.DefaultLocation Write-LogFunctionExit } function Set-LabAzureDefaultLocation { # .ExternalHelp AutomatedLab.Help.xml param ( [Parameter(Mandatory)] [string]$Name ) Write-LogFunctionEntry Update-LabAzureSettings if ($Name -notin $script:lab.AzureSettings.Locations.DisplayName) { Microsoft.PowerShell.Utility\Write-Error "Invalid location. Please specify one of the following locations: $($script:lab.AzureSettings.Locations.DisplayName -join ', ')" return } $script:lab.AzureSettings.DefaultLocation = $script:lab.AzureSettings.Locations | Where-Object DisplayName -eq $Name Write-LogFunctionExit } function Set-LabAzureDefaultStorageAccount { # .ExternalHelp AutomatedLab.Help.xml param ( [Parameter(Mandatory)] [string]$Name ) Write-LogFunctionEntry Update-LabAzureSettings if (-not ($script:lab.AzureSettings.StorageAccounts | Where-Object StorageAccountName -eq $Name)) { Microsoft.PowerShell.Utility\Write-Error "Invalid storage account. Please specify one of the following storage accounts: $($script:lab.AzureSettings.StorageAccounts.StorageAccountName -join ', ')" return } $script:lab.AzureSettings.DefaultStorageAccount = $script:lab.AzureSettings.StorageAccounts | Where-Object StorageAccountName -eq $Name Write-LogFunctionExit } function Get-LabAzureDefaultStorageAccount { # .ExternalHelp AutomatedLab.Help.xml [cmdletbinding()] param () Write-LogFunctionEntry Update-LabAzureSettings if (-not $Script:lab.AzureSettings.DefaultStorageAccount) { Write-Error 'The default storage account is not defined. Use Set-LabAzureDefaultStorageAccount to define it.' return } $Script:lab.AzureSettings.DefaultStorageAccount Write-LogFunctionExit } function New-LabAzureDefaultStorageAccount { # .ExternalHelp AutomatedLab.Help.xml [cmdletbinding()] param ( [Parameter(Mandatory)] [string]$LocationName, [Parameter(Mandatory)] [string]$ResourceGroupName ) Write-LogFunctionEntry Update-LabAzureSettings $storageAccountName = "automatedlab$((1..8 | ForEach-Object { [char[]](97..122) | Get-Random }) -join '')" $param = @{ Name= $storageAccountName ResourceGroupName = $ResourceGroupName Tag = @{ AutomatedLab = $script:lab.Name CreationTime = Get-Date } Sku = 'Standard_LRS' } if ($LocationName) { $location = Get-LabAzureLocation -LocationName $LocationName -ErrorAction Stop $param.Add('Location', $location.DisplayName) Write-ScreenInfo -Message "Creating a new storage account named '$storageAccountName' for location '$($param.Location)'" } $result = New-AzureRmStorageAccount @param -ErrorAction Stop -WarningAction SilentlyContinue if ($result.ProvisioningState -ne 'Succeeded') { throw "Could not create storage account $storageAccountName : $($result.ProvisioningState)" } Write-ScreenInfo -Message 'Storage account now created' $StorageAccount = Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -Name $storageAccountName $ALStorageAccount = [AutomatedLab.Azure.AzureRmStorageAccount]::Create($StorageAccount) $ALStorageAccount.StorageAccountKey = ($StorageAccount | Get-AzureRmStorageAccountKey)[0].Value $script:lab.AzureSettings.StorageAccounts.Add($ALStorageAccount) Write-Verbose "Added $($script:lab.AzureSettings.StorageAccounts.Count) storage accounts" Set-LabAzureDefaultStorageAccount -Name $storageAccountName Write-LogFunctionExit } function Get-LabAzureDefaultResourceGroup { # .ExternalHelp AutomatedLab.Help.xml [cmdletbinding()] param () Write-LogFunctionEntry Update-LabAzureSettings $script:lab.AzureSettings.ResourceGroups | Where-Object ResourceGroupName -eq $script:lab.Name Write-LogFunctionExit } #TODO use keyvault -> New AzureProp defaultKeyVaultName function Import-LabAzureCertificate { # .ExternalHelp AutomatedLab.Help.xml [cmdletbinding()] param () throw New-Object System.NotImplementedException Write-LogFunctionEntry Update-LabAzureSettings $resourceGroup = Get-AzureRmResourceGroup -name (Get-LabAzureDefaultResourceGroup) $keyVault = Get-AzureRmKeyVault -VaultName (Get-LabAzureDefaultKeyVault) -ResourceGroupName $resourceGroup $temp = [System.IO.Path]::GetTempFileName() $cert = ($keyVault | Get-AzureKeyVaultCertificate).Data if ($cert) { $cert | Out-File -FilePath $temp certutil -addstore -f Root $temp | Out-Null Remove-Item -Path $temp Write-LogFunctionExit } else { Write-LogFunctionExitWithError -Message "Could not receive certificate for resource group '$resourceGroup'" } } #TODO use keyvault -> New AzureProp defaultKeyVaultName function New-LabAzureCertificate { # .ExternalHelp AutomatedLab.Help.xml [cmdletbinding()] param () throw New-Object System.NotImplementedException Write-LogFunctionEntry Update-LabAzureSettings $certSubject = "CN=$($Script:lab.Name).cloudapp.net" $service = Get-LabAzureDefaultResourceGroup $cert = dir Cert:\LocalMachine\My | Where-Object Subject -eq $certSubject -ErrorAction SilentlyContinue if (-not $cert) { $temp = [System.IO.Path]::GetTempFileName() #not required as SSL is not used yet #& 'C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\makecert.exe' -r -pe -n $certSubject -b 01/01/2000 -e 01/01/2036 -eku 1.3.6.1.5.5.7.3.1, 1.3.6.1.5.5.7.3.2 -ss my -sr localMachine -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 $temp certutil.exe -addstore -f Root $temp | Out-Null Remove-Item -Path $temp $cert = dir Cert:\LocalMachine\Root | Where-Object Subject -eq $certSubject } #not required as SSL is not used yet #$service | Add-AzureCertificate -CertToDeploy (Get-Item -Path "Cert:\LocalMachine\Root\$($cert.Thumbprint)") } #TODO use keyvault -> New AzureProp defaultKeyVaultName function Get-LabAzureCertificate { # .ExternalHelp AutomatedLab.Help.xml [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])] [cmdletbinding()] param () throw New-Object System.NotImplementedException Write-LogFunctionEntry Update-LabAzureSettings $certSubject = "CN=$($Script:lab.Name).cloudapp.net" $cert = dir Cert:\LocalMachine\My | Where-Object Subject -eq $certSubject -ErrorAction SilentlyContinue if (-not $cert) { #just returning nothing is more convenient #Write-LogFunctionExitWithError -Message "The required certificate does not exist" } else { $cert } Write-LogFunctionExit } function New-LabAzureResourceGroup { # .ExternalHelp AutomatedLab.Help.xml [cmdletbinding()] param ( [Parameter(Mandatory, Position = 0)] [string[]]$ResourceGroupNames, [Parameter(Mandatory, Position = 1)] [string]$LocationName, [switch]$PassThru ) Write-LogFunctionEntry Update-LabAzureSettings Write-Verbose "Creating the resource groups '$($ResourceGroupNames -join ', ')' for location '$LocationName'" $resourceGroups = Get-AzureRmResourceGroup foreach ($name in $ResourceGroupNames) { if ($resourceGroups | Where-Object ResourceGroupName -eq $name) { if(-not $script:lab.AzureSettings.ResourceGroups.ResourceGroupName.Contains($name)) { $script:lab.AzureSettings.ResourceGroups.Add([AutomatedLab.Azure.AzureResourceGroup]::Create((Get-AzureRmResourceGroup -ResourceGroupName $name))) Write-Verbose "The resource group '$name' does already exist" } continue } $result = New-AzureRmResourceGroup -Name $name -Location $LocationName $script:lab.AzureSettings.ResourceGroups.Add([AutomatedLab.Azure.AzureResourceGroup]::Create((Get-AzureRmResourceGroup -ResourceGroupName $name))) if ($PassThru) { $result } Write-Verbose "Resource group '$name' created" } Write-LogFunctionExit } function Remove-LabAzureResourceGroup { # .ExternalHelp AutomatedLab.Help.xml [cmdletbinding()] param ( [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)] [string[]]$ResourceGroupName, [switch]$Force ) begin { Write-LogFunctionEntry Update-LabAzureSettings $resourceGroups = Get-LabAzureResourceGroup } process { Write-ScreenInfo -Message "Removing the Resource Group '$ResourceGroupName'" -Type Warning foreach ($name in $ResourceGroupName) { if ($resourceGroups.ResourceGroupName -contains $name) { Remove-AzureRmResourceGroup -Name $name -Force:$Force -WarningAction SilentlyContinue Write-Verbose "RG '$($name)' removed" $RgObject = $script:lab.AzureSettings.ResourceGroups | Where-Object ResourceGroupName -eq $name $Index = $script:lab.AzureSettings.ResourceGroups.IndexOf($RgObject) $script:lab.AzureSettings.ResourceGroups.RemoveAt($Index) } else { Write-ScreenInfo -Message "RG '$name' could not be found" -Type Error } } } end { Write-LogFunctionExit } } function Get-LabAzureResourceGroup { # .ExternalHelp AutomatedLab.Help.xml [cmdletbinding()] param ( [Parameter(Position = 0)] [string[]]$ResourceGroupName ) Write-LogFunctionEntry Update-LabAzureSettings $resourceGroups = $script:lab.AzureSettings.ResourceGroups if ($ResourceGroupName) { Write-Verbose "Getting the resource groups '$($ResourceGroupName -join ', ')'" $resourceGroups | Where-Object ResourceGroupName -in $ResourceGroupName } else { Write-Verbose 'Getting all resource groups' $resourceGroups } Write-LogFunctionExit } function Add-LabAzureProfile { # .ExternalHelp AutomatedLab.Help.xml [cmdletbinding()] param ( [switch]$PassThru, [switch]$NoDisplay ) Write-LogFunctionEntry $publishSettingFile = (Get-ChildItem -Path (Get-LabSourcesLocation) -Filter '*azurermsettings*' -Recurse | Sort-Object -Property TimeWritten | Select-Object -Last 1).FullName if (-not $NoDisplay) { Write-ScreenInfo -Message "Auto-detected and using publish setting file '$publishSettingFile'" -Type Info } if(-not $publishSettingFile) { return } if($NoDisplay) { $null = Add-LabAzureSubscription -Path $publishSettingFile -PassThru:$PassThru } else { Add-LabAzureSubscription -Path $publishSettingFile -PassThru:$PassThru } Write-LogFunctionExit } function New-LabAzureLabSourcesStorage { # .ExternalHelp AutomatedLab.Help.xml [CmdletBinding()] param () Write-LogFunctionEntry $ResourceGroupName = $script:Lab.AzureSettings.LabSourcesResourceGroupName $StorageAccountName = $script:Lab.AzureSettings.LabSourcesStorageAccountName if(-not $ResourceGroupName) { Write-Verbose 'AutomatedLab lab source resource group not set. Setting it to AutomatedLabSources' $ResourceGroupName = $script:Lab.AzureSettings.LabSourcesResourceGroupName = 'AutomatedLabSources' } $null = New-LabAzureResourceGroup -ResourceGroupNames $ResourceGroupName -LocationName (Get-LabAzureDefaultLocation) if(-not $StorageAccountName) { try { $StorageAccountName = (Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -ErrorAction SilentlyContinue | Where-Object StorageAccountName -like 'automatedlabsources?????')[0].StorageAccountName } catch{ } if(-not $StorageAccountName) { $StorageAccountName = "automatedlabsources$((1..5 | ForEach-Object { [char[]](97..122) | Get-Random }) -join '')" Write-Verbose "Generated random storage account name $StorageAccountName" } else { Write-Verbose "Found and selected existing storage account $StorageAccountName" } } if(-not (Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName | Where-Object StorageAccountName -eq $StorageAccountName)) { Write-Verbose "AutomatedLab lab source storage account '$StorageAccountName' does not exist. Creating it." New-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName -Location (Get-LabAzureDefaultLocation) -Kind Storage -SkuName Standard_LRS | Out-Null } $storageAccount = Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName $alStorageAccount = [AutomatedLab.Azure.AzureRmStorageAccount]::Create($storageAccount) $alStorageAccount.StorageAccountKey = ($storageAccount | Get-AzureRmStorageAccountKey)[0].Value $script:Lab.AzureSettings.LabSourcesStorageAccountName = $StorageAccountName if ($script:Lab.AzureSettings.StorageAccounts.StorageAccountName -contains $StorageAccountName) { $existingGroup = $script:Lab.AzureSettings.StorageAccounts | Where-Object StorageAccountName -eq $StorageAccountName if($existingGroup) { $i = $script:Lab.AzureSettings.StorageAccounts.IndexOf($existingGroup) $script:Lab.AzureSettings.StorageAccounts[$i] = $alStorageAccount } } else { $script:Lab.AzureSettings.StorageAccounts.Add($alStorageAccount) } if(-not (Get-AzureStorageShare -Name 'labsources' -Context $storageAccount.Context -ErrorAction SilentlyContinue)) { Write-Verbose "AutomatedLab lab source file share 'labsources' does not exist. Creating it..." New-AzureStorageShare -Name 'labsources' -Context $storageAccount.Context | Out-Null } Write-Verbose "Successfully selected storage account $StorageAccountName in $ResourceGroupName and created labsources share" Write-LogFunctionExit } function Get-LabAzureLabSourcesStorage { # .ExternalHelp AutomatedLab.Help.xml [CmdletBinding()] param () $StorageAccount = $script:Lab.AzureSettings.StorageAccounts | Where-Object {$_.ResourceGroupName -eq $script:Lab.AzureSettings.LabSourcesResourceGroupName -and $_.StorageAccountName -eq $script:Lab.AzureSettings.LabSourcesStorageAccountName} if(-not $StorageAccount) { $StorageAccount = Get-AzureRmStorageAccount -ResourceGroupName $script:Lab.AzureSettings.LabSourcesResourceGroupName -Name $script:Lab.AzureSettings.LabSourcesStorageAccountName $StorageAccount | Add-Member -MemberType NoteProperty -Name StorageAccountKey -Value ($storageAccount | Get-AzureRmStorageAccountKey)[0].Value } $StorageAccount | Add-Member -MemberType NoteProperty -Name 'Path' -Value "\\$($script:Lab.AzureSettings.LabSourcesStorageAccountName).file.core.windows.net\labsources" -Force $StorageAccount } function Remove-LabAzureLabSourcesStorage { # .ExternalHelp AutomatedLab.Help.xml [CmdletBinding()] param () Remove-LabAzureResourceGroup -ResourceGroupName $script:Lab.AzureSettings.LabSourcesResourceGroupName -Force } function Sync-LabAzureLabSources { # .ExternalHelp AutomatedLab.Help.xml [CmdletBinding()] param () Write-LogFunctionExit # Retrieve storage context $StorageAccount = Get-AzureRmStorageAccount -ResourceGroupName $script:Lab.AzureSettings.LabSourcesResourceGroupName -Name $script:Lab.AzureSettings.LabSourcesStorageAccountName $AccountKey = ($StorageAccount | Get-AzureRmStorageAccountKey)[0].Value Unblock-LabSources -Path (Get-LabSourcesLocationInternal -Local) # Create the empty folders first foreach($Folder in (Get-ChildItem -Path (Get-LabSourcesLocationInternal -Local) -Recurse -Directory)) { $err = $Null $FolderName = $Folder.FullName.Replace("$(Get-LabSourcesLocationInternal -Local)\",'') # Use an error variable and check the HttpStatusCode since there is no cmdlet to get or test a StorageDirectory $null = New-AzureStorageDirectory -Share (Get-AzureStorageShare -Name labsources -Context $StorageAccount.Context) -Path $FolderName -ErrorVariable err -ErrorAction SilentlyContinue Write-Verbose "Created directory $FolderName in labsources" if($err) { if($err[0].Exception.RequestInformation.HttpStatusCode -ne 409) { throw "An error ocurred during file upload: $($err[0].Exception.Message)" } } } # Sync the lab sources foreach($File in (Get-ChildItem -Path (Get-LabSourcesLocationInternal -Local) -Recurse -File)) { # Check if file is an OS ISO and skip if($File.Extension -eq '.iso') { $IsoDefinition = Get-LabIsoImageDefinition | Where-Object {$_.Path -EQ $File.FullName -and $_.IsOperatingSystem} if($IsoDefinition) { Write-Verbose "Skipping OS ISO $($File.FullName)" continue } } $FileName = $File.FullName.Replace("$(Get-LabSourcesLocationInternal -Local)\",'') $AzureFile = Get-AzureStorageFile -Share (Get-AzureStorageShare -Name labsources -Context $StorageAccount.Context) -Path $FileName -ErrorAction SilentlyContinue if($AzureFile) { $AzureHash = $AzureFile.Properties.ContentMD5 $FileHash = (Get-FileHash -Path $File.FullName -Algorithm MD5).Hash Write-Verbose "$FileName already exists in Azure. Source hash is $FileHash and Azure hash is $AzureHash" } if(-not $AzureFile -or ($AzureFile -and $FileHash -ne $AzureHash)) { $null = Set-AzureStorageFileContent -Share (Get-AzureStorageShare -Name labsources -Context $StorageAccount.Context) -Source $File.FullName -Path $FileName -ErrorAction SilentlyContinue Write-Verbose "Azure file $FileName successfully uploaded. Generating file hash..." } # Try to set the file hash $UploadedFile = Get-AzureStorageFile -Share (Get-AzureStorageShare -Name labsources -Context $StorageAccount.Context) -Path $FileName -ErrorAction SilentlyContinue $UploadedFile.Properties.ContentMD5 = (Get-FileHash -Path $File.FullName -Algorithm MD5).Hash $ApiResponse = $UploadedFile.SetPropertiesAsync() if(-not $ApiResponse.Status -eq "RanToCompletion") { Write-Warning "Could not generate MD5 hash for file $FileName. Status was $($ApiResponse.Status)" continue } Write-Verbose "Azure file $FileName successfully uploaded and hash generated" Write-LogFunctionExit } } |