AzureRM.Bootstrapper.psm1
$RollUpModule = "AzureRM" $PSProfileMapEndpoint = "https://azureprofile.azureedge.net/powershell/profilemap.json" $script:BootStrapRepo = "BootStrap" $RepoLocation = "https://www.powershellgallery.com/api/v2/" $existingRepos = Get-PSRepository | Where-Object {$_.SourceLocation -eq $RepoLocation} if ($null -eq $existingRepos) { Register-PSRepository -Name $BootStrapRepo -SourceLocation $RepoLocation -PublishLocation $RepoLocation -ScriptSourceLocation $RepoLocation -ScriptPublishLocation $RepoLocation -InstallationPolicy Trusted -PackageManagementProvider NuGet } else { $script:BootStrapRepo = $existingRepos[0].Name } # Is it Powershell Core edition? $Script:IsCoreEdition = ($PSVersionTable.PSEdition -eq 'Core') # Check if current user is Admin to decide on cache path $script:IsAdmin = $false if ((-not $Script:IsCoreEdition) -or ($IsWindows)) { $script:ProgramFilesPSPath = $env:ProgramFiles If (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { $script:IsAdmin = $true } } else { $script:ProgramFilesPSPath = $PSHOME # on Linux, tests run via sudo will generally report "root" for whoami if ( (whoami) -match "root" ) { $script:IsAdmin = $true } } # Get profile cache path function Get-ProfileCachePath { if ((-not $Script:IsCoreEdition) -or ($IsWindows)) { $ProfileCache = Join-Path -path $env:LOCALAPPDATA -childpath "Microsoft\AzurePowerShell\ProfileCache" if ($script:IsAdmin) { $ProfileCache = Join-Path -path $env:ProgramData -ChildPath "Microsoft\AzurePowerShell\ProfileCache" } } else { $ProfileCache = "$HOME/.config/Microsoft/AzurePowerShell/ProfileCache" } # If profile cache directory does not exist, create one. if(-Not (Test-Path $ProfileCache)) { New-Item -ItemType Directory -Force -Path $ProfileCache | Out-Null } return $ProfileCache } # Function to find the latest profile map from cache function Get-LatestProfileMapPath { $ProfileCache = Get-ProfileCachePath $ProfileMapPaths = Get-ChildItem $ProfileCache if ($null -eq $ProfileMapPaths) { return } $LargestNumber = Get-LargestNumber -ProfileCache $ProfileCache if ($null -eq $LargestNumber) { return } $LatestMapPath = $ProfileMapPaths | Where-Object { $_.Name.Startswith($LargestNumber.ToString() + '-') } return $LatestMapPath } # Function to get the largest number in profile cache profile map names: This helps to find the latest map function Get-LargestNumber { param($ProfileCache) $ProfileMapPaths = Get-ChildItem $ProfileCache $LargestNumber = $ProfileMapPaths | ForEach-Object { if($_.Name -match "\d+-") { $matches[0] -replace '-' } } | Measure-Object -Maximum if ($null -ne $LargestNumber) { return $LargestNumber.Maximum } } # Find the latest ProfileMap $script:LatestProfileMapPath = Get-LatestProfileMapPath # Make Web-Call function Get-AzureStorageBlob { $ScriptBlock = { Invoke-WebRequest -uri $PSProfileMapEndpoint -ErrorVariable RestError } $WebResponse = Invoke-CommandWithRetry -ScriptBlock $ScriptBlock return $WebResponse } # Get-ProfileMap from Azure Endpoint function Get-AzureProfileMap { Write-Verbose "Updating profiles" $ProfileCache = Get-ProfileCachePath # Get online profile data using Web request $WebResponse = Get-AzureStorageBlob # Get ETag value for OnlineProfileMap $OnlineProfileMapETag = $WebResponse.Headers["ETag"] # If profilemap json exists, compare online Etag and cached Etag; if not different, don't replace cache. if (($null -ne $script:LatestProfileMapPath) -and ($script:LatestProfileMapPath -match "(\d+)-(.*.json)")) { [string]$ProfileMapETag = [System.IO.Path]::GetFileNameWithoutExtension($Matches[2]) if (($ProfileMapETag -eq $OnlineProfileMapETag) -and (Test-Path $script:LatestProfileMapPath.FullName)) { $scriptBlock = { Get-Content -Raw -Path $script:LatestProfileMapPath.FullName -ErrorAction stop | ConvertFrom-Json } $ProfileMap = Invoke-CommandWithRetry -ScriptBlock $scriptBlock if ($null -ne $ProfileMap) { return $ProfileMap } } } # If profilemap json doesn't exist, or if online ETag and cached ETag are different, cache online profile map $LargestNoFromCache = Get-LargestNumber -ProfileCache $ProfileCache if ($null -eq $LargestNoFromCache) { $LargestNoFromCache = 0 } $ChildPathName = ($LargestNoFromCache+1).ToString() + '-' + ($OnlineProfileMapETag) + ".json" $CacheFilePath = (Join-Path $ProfileCache -ChildPath $ChildPathName) $OnlineProfileMap = RetrieveProfileMap -WebResponse $WebResponse $OnlineProfileMap | ConvertTo-Json -Compress | Out-File -FilePath $CacheFilePath # Store old profile map's path before Updating $oldProfileMap = $script:LatestProfileMapPath # Update $script:LatestProfileMapPath $script:LatestProfileMapPath = Get-ChildItem $ProfileCache | Where-Object { $_.FullName.equals($CacheFilePath)} # Remove old profile map if it exists if (($null -ne $oldProfileMap) -and (Test-Path $oldProfileMap.FullName)) { $ScriptBlock = { Remove-Item -Path $oldProfileMap.FullName -Force -ErrorAction Stop } Invoke-CommandWithRetry -ScriptBlock $ScriptBlock } return $OnlineProfileMap } # Helper to retrieve profile map from http response function RetrieveProfileMap { param($WebResponse) $OnlineProfileMap = $WebResponse | ConvertFrom-Json return $OnlineProfileMap } # Get ProfileMap from Cache, online or embedded source function Get-AzProfile { [CmdletBinding()] param([Switch]$Update) $Update = $PSBoundParameters.Update # If Update is present, download ProfileMap from online source if ($Update.IsPresent) { return (Get-AzureProfileMap) } # Check the cache if(($null -ne $script:LatestProfileMapPath) -and (Test-Path $script:LatestProfileMapPath.FullName)) { $scriptBlock = { Get-Content -Raw -Path $script:LatestProfileMapPath.FullName -ErrorAction stop | ConvertFrom-Json } $ProfileMap = Invoke-CommandWithRetry -ScriptBlock $scriptBlock if ($null -ne $ProfileMap) { return $ProfileMap } } # If cache doesn't exist, Check embedded source $defaults = [System.IO.Path]::GetDirectoryName($PSCommandPath) $scriptBlock = { Get-Content -Raw -Path (Join-Path -Path $defaults -ChildPath "ProfileMap.json") -ErrorAction stop | ConvertFrom-Json } $ProfileMap = Invoke-CommandWithRetry -ScriptBlock $scriptBlock if($null -eq $ProfileMap) { # Cache & Embedded source empty; Return error and stop throw [System.IO.FileNotFoundException] "Profile meta data does not exist. Use 'Get-AzureRmProfile -Update' to download from online source." } return $ProfileMap } # Lists the profiles that are installed on the machine function Get-ProfilesInstalled { param([parameter(Mandatory = $true)] [PSCustomObject] $ProfileMap, [REF]$IncompleteProfiles) $result = @{} $AllProfiles = ($ProfileMap | Get-Member -MemberType NoteProperty).Name foreach ($key in $AllProfiles) { Write-Verbose "Checking if profile $key is installed" foreach ($module in ($ProfileMap.$key | Get-Member -MemberType NoteProperty).Name) { $ModulesList = (Get-Module -Name $Module -ListAvailable) $versionList = $ProfileMap.$key.$module foreach ($version in $versionList) { if ($null -ne ($ModulesList | Where-Object { $_.Version -eq $version})) { if ($result.ContainsKey($key)) { if ($result[$key].Containskey($module)) { $result[$key].$module += $version } else { $result[$key] += @{$module = @($version)} } } else { $result.Add($key, @{$module = @($version)}) } } } } # If not all the modules from a profile are installed, add it to $IncompleteProfiles if(($result.$key.Count -gt 0) -and ($result.$key.Count -ne ($ProfileMap.$key | Get-Member -MemberType NoteProperty).Count)) { if ($result.$key.Contains($RollUpModule)) { continue } $result.Remove($key) if ($null -ne $IncompleteProfiles) { $IncompleteProfiles.Value += $key } } } return $result } # Get profiles installed associated with the module version function Test-ProfilesInstalled { param([System.Version]$version, [String]$Module, [String]$Profile, [PSObject]$PMap, [hashtable]$AllProfilesInstalled) # Profiles associated with the particular module version - installed? $profilesAssociated = @() foreach ($profileInAllProfiles in $AllProfilesInstalled[$Module + $version]) { $profilesAssociated += $profileInAllProfiles } return $profilesAssociated } # Function to uninstall module function Uninstall-ModuleHelper { [CmdletBinding(SupportsShouldProcess = $true)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidShouldContinueWithoutForce", "")] param([String]$Profile, $Module, [System.Version]$version, [Switch]$RemovePreviousVersions) $Remove = $PSBoundParameters.RemovePreviousVersions Do { $moduleInstalled = Get-Module -Name $Module -ListAvailable | Where-Object { $_.Version -eq $version} if ($PSCmdlet.ShouldProcess("$module version $version", "Remove module")) { if (($null -ne $moduleInstalled) -and ($Remove.IsPresent -or $PSCmdlet.ShouldContinue("Uninstall module $Module version $version", "Uninstall Modules for profile $Profile"))) { Write-Verbose "Removing module from session" Remove-Module -Name $module -Force -ErrorAction "SilentlyContinue" try { Write-Verbose "Uninstalling module $module version $version" Uninstall-Module -Name $module -RequiredVersion $version -Force -ErrorAction Stop } catch { if ($_.Exception.Message -match "No match was found") { # Check for msi installation (Install folder: C:\ProgramFiles(x86)\Microsoft SDKs\Azure\PowerShell) Only in windows if ((-not $Script:IsCoreEdition) -or ($IsWindows)) { $sdkPath1 = (join-path ${env:ProgramFiles(x86)} -childpath "\Microsoft SDKs\Azure\PowerShell\") $sdkPath2 = (join-path $script:ProgramFilesPSPath -childpath "\Microsoft SDKs\Azure\PowerShell\") if (($null -ne $moduleInstalled.Path) -and (($moduleInstalled.Path.Contains($sdkPath1) -or $moduleInstalled.Path.Contains($sdkPath2)))) { Write-Error "Unable to uninstall module $module because it was installed in a different scope than expected. If you installed via an MSI, please uninstall the MSI before proceeding." -Category InvalidOperation break } } Write-Error "Unable to uninstall module $module because it was installed in a different scope than expected. If you installed the module to a custom directory in your path, please remove the module manually, by using Uninstall-Module, or removing the module directory." -Category InvalidOperation } else { Write-Error $_.Exception.Message } break } } else { break } } else { break } } While($null -ne $moduleInstalled); } # Help function to uninstall a profile function Uninstall-ProfileHelper { [CmdletBinding()] param([PSObject]$PMap, [String]$Profile, [Switch]$Force) $modules = ($PMap.$Profile | Get-Member -MemberType NoteProperty).Name # Get-Profiles installed across all hashes. This is to avoid uninstalling modules that are part of other installed profiles $AllProfilesInstalled = Get-AllProfilesInstalled foreach ($module in $modules) { $versionList = $PMap.$Profile.$module foreach ($version in $versionList) { if ($Force.IsPresent) { Invoke-UninstallModule -PMap $PMap -Profile $Profile -Module $module -version $version -AllProfilesInstalled $AllProfilesInstalled -RemovePreviousVersions } else { Invoke-UninstallModule -PMap $PMap -Profile $Profile -Module $module -version $version -AllProfilesInstalled $AllProfilesInstalled } } } } # Checks if the module is part of other installed profiles. Calls Uninstall-ModuleHelper if not. function Invoke-UninstallModule { [CmdletBinding()] param([PSObject]$PMap, [String]$Profile, $Module, [System.Version]$version, [hashtable]$AllProfilesInstalled, [Switch]$RemovePreviousVersions) # Check if the profiles associated with the module version are installed. Write-Verbose "Checking module dependency to any other profile installed" $profilesAssociated = Test-ProfilesInstalled -version $version -Module $Module -Profile $Profile -PMap $PMap -AllProfilesInstalled $AllProfilesInstalled # If more than one profile is installed for the same version of the module, do not uninstall if ($profilesAssociated.Count -gt 1) { return } $PSBoundParameters.Remove('AllProfilesInstalled') | Out-Null $PSBoundParameters.Remove('PMap') | Out-Null Uninstall-ModuleHelper @PSBoundParameters } # Helps to uninstall previous versions of modules in the profile function Remove-PreviousVersion { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param([PSObject]$LatestMap, [hashtable]$AllProfilesInstalled, [String]$Profile, [Array]$Module, [Switch]$RemovePreviousVersions) $Remove = $PSBoundParameters.RemovePreviousVersions $Modules = $PSBoundParameters.Module Write-Verbose "Checking if previous versions of modules are installed" if ($null -eq $Modules) { $Modules = ($LatestMap.$Profile | Get-Member -MemberType NoteProperty).Name } foreach ($module in $Modules) { # Skip the latest version; first element will be the latest version $versionList = $LatestMap.$Profile.$module $versionList = $versionList | Where-Object { $_ -ne $versionList[0] } foreach ($version in $versionList) { # Is that module version installed? If not skip; if ($null -eq (Get-Module -Name $Module -ListAvailable | Where-Object { $_.Version -eq $version} )) { continue } Write-Verbose "Previous versions of modules were found. Trying to uninstall..." if ($Remove.IsPresent) { Invoke-UninstallModule -PMap $LatestMap -Profile $Profile -Module $module -version $version -AllProfilesInstalled $AllProfilesInstalled -RemovePreviousVersions } else { Invoke-UninstallModule -PMap $LatestMap -Profile $Profile -Module $module -version $version -AllProfilesInstalled $AllProfilesInstalled } } # Uninstall removes module from session; import latest version again $versions = $LatestMap.$Profile.$module $version = Get-LatestModuleVersion -versions $versions Import-Module $Module -RequiredVersion $version -Global } } # Gets profiles installed as @{Module+Version = @(profile)} for checking module dependency during uninstall function Get-AllProfilesInstalled { $AllProfilesInstalled = @{} # If Cache is empty, use embedded source if ($null -eq $script:LatestProfileMapPath) { $ModulePath = [System.IO.Path]::GetDirectoryName($PSCommandPath) $script:LatestProfileMapPath = Get-Item -Path (Join-Path -Path $ModulePath -ChildPath "ProfileMap.json") } $scriptBlock = { Get-Content -Raw -Path $script:LatestProfileMapPath.FullName -ErrorAction stop | ConvertFrom-Json } $ProfileMap = Invoke-CommandWithRetry -ScriptBlock $scriptBlock $profilesInstalled = (Get-ProfilesInstalled -ProfileMap $ProfileMap) foreach ($Profile in $profilesInstalled.Keys) { foreach ($module in ($profilesinstalled.$profile.Keys)) { $versionList = $profilesinstalled.$Profile.$Module foreach ($version in $versionList) { if ($AllProfilesInstalled.ContainsKey(($Module + $version))) { if ($Profile -notin $AllProfilesInstalled[($Module + $version)]) { $AllProfilesInstalled[($Module + $version)] += $Profile } } else { $AllProfilesInstalled.Add(($Module + $version), @($Profile)) } } } } return $AllProfilesInstalled } # Helps to remove-previous versions of the update-profile and clean up cache function Update-ProfileHelper { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param([String]$Profile, [Array]$Module, [Switch]$RemovePreviousVersions) Write-Verbose "Attempting to clean up previous versions" # Cache was updated before calling this function, so latestprofilemap will not be null. $scriptBlock = { Get-Content -Raw -Path $script:LatestProfileMapPath.FullName -ErrorAction stop | ConvertFrom-Json } $LatestProfileMap = Invoke-CommandWithRetry -ScriptBlock $scriptBlock $AllProfilesInstalled = Get-AllProfilesInstalled Remove-PreviousVersion -LatestMap $LatestProfileMap -AllProfilesInstalled $AllProfilesInstalled @PSBoundParameters } # If cmdlets were installed at a different scope, warn users of the potential conflict function Find-PotentialConflict { [CmdletBinding(SupportsShouldProcess = $true)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] param([string]$Module, [switch]$Force) Write-Verbose "Checking if there is a potential conflict for module installation" $availableModules = Get-Module $Module -ListAvailable $IsPotentialConflict = $false Write-Information "Modules installed: $availableModules" if ($null -eq $availableModules) { return $false } # If Admin, check CurrentUser Module folder path and vice versa if ($script:IsAdmin) { $availableModules | ForEach-Object { if (($null -ne $_.Path) -and $_.Path.Contains($HOME)) { $IsPotentialConflict = $true } } } else { $availableModules | ForEach-Object { if (($null -ne $_.Path) -and $_.Path.Contains($script:ProgramFilesPSPath)) { $IsPotentialConflict = $true } } } # If potential conflict found, confirm with user for continuing with module installation if 'force' was not used if ($IsPotentialConflict) { if (($Force.IsPresent) -or ($PSCmdlet.ShouldContinue(` "The Cmdlets from module $Module are already present on this device. Proceeding with the installation might cause conflicts. Would you like to continue?", "Detected $Module cmdlets"))) { return $false } else { return $true } } # False if no conflict was found return $false } # Helper function to invoke install-module function Invoke-InstallModule { param($module, $version, $scope) $installCmd = Get-Command Install-Module if($installCmd.Parameters.ContainsKey('AllowClobber')) { if (-not $scope) { Install-Module $Module -RequiredVersion $version -AllowClobber -Repository $script:BootStrapRepo } else { Install-Module $Module -RequiredVersion $version -Scope $scope -AllowClobber -Repository $script:BootStrapRepo } } else { if (-not $scope) { Install-Module $Module -RequiredVersion $version -Force -Repository $script:BootStrapRepo } else { Install-Module $Module -RequiredVersion $version -Scope $scope -Force -Repository $script:BootStrapRepo } } } # Invoke any script block with a retry logic function Invoke-CommandWithRetry { [CmdletBinding()] [OutputType([PSObject])] Param ( [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] [System.Management.Automation.ScriptBlock] $ScriptBlock, [Parameter(Position=1)] [ValidateNotNullOrEmpty()] [int]$MaxRetries=3, [Parameter(Position=2)] [ValidateNotNullOrEmpty()] [int]$RetryDelay=3 ) Begin { $currentRetry = 1 $Success = $False } Process { do { try { $result = . $ScriptBlock $success = $true return $result } catch { $currentRetry = $currentRetry + 1 if ($currentRetry -gt $MaxRetries) { $PSCmdlet.ThrowTerminatingError($PSitem) } else { Write-verbose -Message "Waiting $RetryDelay second(s) before attempting again" Start-Sleep -seconds $RetryDelay } } } while(-not $Success) } } # Select profile according to scope & create if it doesn't exist function Select-Profile { param([string]$scope) if($scope -eq "AllUsers" -and (-not $script:IsAdmin)) { Write-Error "Administrator rights are required to use AllUsers scope. Log on to the computer with an account that has Administrator rights, and then try again, or retry the operation by adding `"-Scope CurrentUser`" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). " -Category InvalidArgument -ErrorAction Stop } if($scope -eq "AllUsers") { $profilePath = $profile.AllUsersAllHosts } else { $profilePath = $profile.CurrentUserAllHosts } if (-not (Test-Path $ProfilePath)) { new-item -path $ProfilePath -itemtype file -force | Out-Null } return $profilePath } # Get the latest version of a module in a profile function Get-LatestModuleVersion { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] param ([array]$versions) $versionEnum = $versions.GetEnumerator() $toss = $versionEnum.MoveNext() $version = $versionEnum.Current return $version } # Gets module version to be set in default parameter in $profile function Get-ModuleVersion { param ([string] $armProfile, [string] $invocationLine) if (-not $invocationLine.ToLower().Contains("azure")) { return } $ProfileMap = (Get-AzProfile) $Modules = ($ProfileMap.$armProfile | Get-Member -MemberType NoteProperty).Name # Check for AzureRm first if ($invocationLine.ToLower().Contains($RollUpModule.ToLower()) -and (-not $invocationLine.ToLower().Contains("$($RollUpModule.ToLower())."))) { $versions = $ProfileMap.$armProfile.$RollUpModule $version = Get-LatestModuleVersion -versions $versions return $version } foreach ($module in $Modules) { if ($module -eq $RollUpModule) { continue } if ($invocationLine.ToLower().Contains($module.ToLower())) { $versions = $ProfileMap.$armProfile.$module $version = Get-LatestModuleVersion -versions $versions return $version } } } # Create a script block with function to be called to get requiredversions for use with default parameters function Get-ScriptBlock { param ($ProfilePath) $profileContent = @() # Write Get-ModuleVersion function to $profile path $functionScript = @" function Get-ModVersion { param (`$armProfile, `$invocationLine) if (-not `$invocationLine.ToLower().Contains("azure")) { return } try { `$BootstrapModule = Get-Module -Name "AzureRM.Bootstrapper" -ListAvailable if (`$null -ne `$BootstrapModule) { Import-Module -Name "AzureRM.Bootstrapper" -RequiredVersion `$BootstrapModule.Version[0] `$version = Get-ModuleVersion -armProfile `$armProfile -invocationLine `$invocationLine return `$version } } catch { return } } `r`n "@ $profileContent += $functionScript $defaultScript = @" `$PSDefaultParameterValues["Import-Module:RequiredVersion"]={ Get-ModVersion -armProfile `$PSDefaultParameterValues["*-AzureRmProfile:Profile"] -invocationLine `$MyInvocation.Line } ########## END AzureRM.Bootstrapper scripts "@ $profileContent += $defaultScript return $profileContent } function Remove-ProfileSetting { [CmdletBinding(SupportsShouldProcess=$true)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] param ([string] $profilePath) $RemoveSettingScriptBlock = { $reqLines = @() Get-Content -Path $profilePath -ErrorAction Stop | Foreach-Object { if($_.contains("BEGIN AzureRM.Bootstrapper scripts") -or $donotread) { $donotread = $true; } else { $reqLines += $_ } if ($_.contains("END AzureRM.Bootstrapper scripts")) { $donotread = $false } } if ($PSCmdlet.ShouldProcess($reqLines, "Updating `$profile conents")) { Set-Content -path $profilePath -Value $reqLines -ErrorAction Stop } } Invoke-CommandWithRetry -ScriptBlock $RemoveSettingScriptBlock } # Add Scope parameter to the cmdlet function Add-ScopeParam { param([System.Management.Automation.RuntimeDefinedParameterDictionary]$params, [string]$set = "__AllParameterSets") $Keys = @('CurrentUser', 'AllUsers') $scopeValid = New-Object -Type System.Management.Automation.ValidateSetAttribute($Keys) $scopeAttribute = New-Object -Type System.Management.Automation.ParameterAttribute $scopeAttribute.ParameterSetName = $scopeAttribute.Mandatory = $false $scopeAttribute.Position = 2 $scopeCollection = New-object -Type System.Collections.ObjectModel.Collection[System.Attribute] $scopeCollection.Add($scopeValid) $scopeCollection.Add($scopeAttribute) $scopeParam = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Scope", [string], $scopeCollection) $params.Add("Scope", $scopeParam) } # Add the profile parameter to the cmdlet function Add-ProfileParam { param([System.Management.Automation.RuntimeDefinedParameterDictionary]$params, [string]$set = "__AllParameterSets") $ProfileMap = (Get-AzProfile) $AllProfiles = ($ProfileMap | Get-Member -MemberType NoteProperty).Name $profileAttribute = New-Object -Type System.Management.Automation.ParameterAttribute $profileAttribute.ParameterSetName = $set $profileAttribute.Mandatory = $true $profileAttribute.Position = 0 $profileAttribute.ValueFromPipeline = $true $validateProfileAttribute = New-Object -Type System.Management.Automation.ValidateSetAttribute($AllProfiles) $profileCollection = New-object -Type System.Collections.ObjectModel.Collection[System.Attribute] $profileCollection.Add($profileAttribute) $profileCollection.Add($validateProfileAttribute) $profileParam = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Profile", [string], $profileCollection) $params.Add("Profile", $profileParam) } function Add-ForceParam { param([System.Management.Automation.RuntimeDefinedParameterDictionary]$params, [string]$set = "__AllParameterSets") Add-SwitchParam $params "Force" $set } function Add-RemoveParam { param([System.Management.Automation.RuntimeDefinedParameterDictionary]$params, [string]$set = "__AllParameterSets") $name = "RemovePreviousVersions" $newAttribute = New-Object -Type System.Management.Automation.ParameterAttribute $newAttribute.ParameterSetName = $set $newAttribute.Mandatory = $false $newCollection = New-object -Type System.Collections.ObjectModel.Collection[System.Attribute] $newCollection.Add($newAttribute) $newParam = New-Object -Type System.Management.Automation.RuntimeDefinedParameter($name, [switch], $newCollection) $params.Add($name, [Alias("r")]$newParam) } function Add-SwitchParam { param([System.Management.Automation.RuntimeDefinedParameterDictionary]$params, [string]$name, [string] $set = "__AllParameterSets") $newAttribute = New-Object -Type System.Management.Automation.ParameterAttribute $newAttribute.ParameterSetName = $set $newAttribute.Mandatory = $false $newCollection = New-object -Type System.Collections.ObjectModel.Collection[System.Attribute] $newCollection.Add($newAttribute) $newParam = New-Object -Type System.Management.Automation.RuntimeDefinedParameter($name, [switch], $newCollection) $params.Add($name, $newParam) } function Add-ModuleParam { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] param([System.Management.Automation.RuntimeDefinedParameterDictionary]$params, [string]$name, [string] $set = "__AllParameterSets") $ProfileMap = (Get-AzProfile) $Profiles = ($ProfileMap | Get-Member -MemberType NoteProperty).Name if ($Profiles.Count -gt 1) { $enum = $Profiles.GetEnumerator() $toss = $enum.MoveNext() $Current = $enum.Current $Keys = ($($ProfileMap.$Current) | Get-Member -MemberType NoteProperty).Name } else { $Keys = ($($ProfileMap.$Profiles[0]) | Get-Member -MemberType NoteProperty).Name } $moduleValid = New-Object -Type System.Management.Automation.ValidateSetAttribute($Keys) $AllowNullAttribute = New-Object -Type System.Management.Automation.AllowNullAttribute $AllowEmptyStringAttribute = New-Object System.Management.Automation.AllowEmptyStringAttribute $moduleAttribute = New-Object -Type System.Management.Automation.ParameterAttribute $moduleAttribute.ParameterSetName = $moduleAttribute.Mandatory = $false $moduleAttribute.Position = 1 $moduleCollection = New-object -Type System.Collections.ObjectModel.Collection[System.Attribute] $moduleCollection.Add($moduleValid) $moduleCollection.Add($moduleAttribute) $moduleCollection.Add($AllowNullAttribute) $moduleCollection.Add($AllowEmptyStringAttribute) $moduleParam = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Module", [array], $moduleCollection) $params.Add("Module", $moduleParam) } <# .ExternalHelp help\AzureRM.Bootstrapper-help.xml #> function Get-AzureRmModule { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] param() DynamicParam { $params = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary Add-ProfileParam $params $ProfileMap = (Get-AzProfile) $Profiles = ($ProfileMap | Get-Member -MemberType NoteProperty).Name if ($Profiles.Count -gt 1) { $enum = $Profiles.GetEnumerator() $toss = $enum.MoveNext() $Current = $enum.Current $Keys = ($($ProfileMap.$Current) | Get-Member -MemberType NoteProperty).Name } else { $Keys = ($($ProfileMap.$Profiles[0]) | Get-Member -MemberType NoteProperty).Name } $moduleValid = New-Object -Type System.Management.Automation.ValidateSetAttribute($Keys) $moduleAttribute = New-Object -Type System.Management.Automation.ParameterAttribute $moduleAttribute.ParameterSetName = $moduleAttribute.Mandatory = $true $moduleAttribute.Position = 1 $moduleCollection = New-object -Type System.Collections.ObjectModel.Collection[System.Attribute] $moduleCollection.Add($moduleValid) $moduleCollection.Add($moduleAttribute) $moduleParam = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Module", [string], $moduleCollection) $params.Add("Module", $moduleParam) return $params } PROCESS { $ProfileMap = (Get-AzProfile) $Profile = $PSBoundParameters.Profile $Module = $PSBoundParameters.Module $versionList = $ProfileMap.$Profile.$Module Write-Verbose "Getting the version of $module from $profile" $moduleList = Get-Module -Name $Module -ListAvailable | Where-Object {$null -ne $_.RepositorySourceLocation} foreach ($version in $versionList) { foreach ($module in $moduleList) { if ($version -eq $module.Version) { return $version } } } return $null } } <# .ExternalHelp help\AzureRM.Bootstrapper-help.xml #> function Get-AzureRmProfile { [CmdletBinding(DefaultParameterSetName="ListAvailableParameterSet")] param() DynamicParam { $params = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary Add-SwitchParam $params "ListAvailable" "ListAvailableParameterSet" Add-SwitchParam $params "Update" return $params } PROCESS { # ListAvailable helps to display all profiles available from the gallery [switch]$ListAvailable = $PSBoundParameters.ListAvailable $PSBoundParameters.Remove('ListAvailable') | Out-Null $ProfileMap = (Get-AzProfile @PSBoundParameters) if ($ListAvailable.IsPresent) { Write-Verbose "Getting all the profiles available for install" foreach ($profile in ($ProfileMap | get-member -MemberType NoteProperty).Name) { $profileObj = $ProfileMap.$profile $profileObj | Add-Member -MemberType NoteProperty -Name "ProfileName" -Value $profile $profileObj | Add-Member -TypeName ProfileMapData $profileObj } return } else { # Just display profiles installed on the machine Write-Verbose "Getting profiles installed on the machine and available for import" $IncompleteProfiles = @() $profilesInstalled = Get-ProfilesInstalled -ProfileMap $ProfileMap ([REF]$IncompleteProfiles) foreach ($key in $profilesInstalled.Keys) { $profileObj = New-Object -TypeName psobject -property $profilesinstalled.$key $profileObj.PSObject.TypeNames.Insert(0,'ProfileMapData') $profileObj | Add-Member -MemberType NoteProperty -Name "ProfileName" -Value $key $profileObj } if ($IncompleteProfiles.Count -gt 0) { Write-Warning "Some modules from profile(s) $(@($IncompleteProfiles) -join ', ') were not installed. Use Install-AzureRmProfile to install missing modules." } return } } } <# .ExternalHelp help\AzureRM.Bootstrapper-help.xml #> function Use-AzureRmProfile { [CmdletBinding(SupportsShouldProcess=$true)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidShouldContinueWithoutForce", "")] param() DynamicParam { $params = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary Add-ProfileParam $params Add-ForceParam $params Add-ScopeParam $params Add-ModuleParam $params return $params } PROCESS { $Force = $PSBoundParameters.Force $ProfileMap = (Get-AzProfile) $Profile = $PSBoundParameters.Profile $Scope = $PSBoundParameters.Scope $Modules = $PSBoundParameters.Module # If user hasn't provided modules, use the module names from profile if ($null -eq $Modules) { $Modules = ($ProfileMap.$Profile | Get-Member -MemberType NoteProperty).Name } # If AzureRM $RollUpModule is present in that profile, it will install all the dependent modules; no need to specify other modules if ($Modules.Contains($RollUpModule)) { $Modules = @($RollUpModule) } $PSBoundParameters.Remove('Profile') | Out-Null $PSBoundParameters.Remove('Scope') | Out-Null $PSBoundParameters.Remove('Module') | Out-Null # Variable to track progress $ModuleCount = 0 Write-Output "Loading Profile $Profile" foreach ($Module in $Modules) { $ModuleCount = $ModuleCount + 1 $version = Get-AzureRmModule -Profile $Profile -Module $Module if (($null -eq $version) -and $PSCmdlet.ShouldProcess($module, "Installing module for profile $profile in the current scope")) { Write-Verbose "$module was not found on the machine. Trying to install..." if (($Force.IsPresent -or $PSCmdlet.ShouldContinue("Install Module $module for Profile $Profile from the gallery?", "Installing Modules for Profile $Profile"))) { if (Find-PotentialConflict -Module $Module @PSBoundParameters) { continue } $versions = $ProfileMap.$Profile.$Module $version = Get-LatestModuleVersion -versions $versions Write-Progress -Activity "Installing Module $Module version: $version" -Status "Progress:" -PercentComplete ($ModuleCount/($Modules.Length)*100) Write-Verbose "Installing module $module" Invoke-InstallModule -module $Module -version $version -scope $scope } } # If a different profile's Azure Module was imported, block user $importedModules = Get-Module "Azure*" foreach ($importedModule in $importedModules) { $importedVersions = $ProfileMap.$Profile.$($importedModule.Name) if ($null -ne $importedVersions) { # We need the latest version in that profile to be imported. If old version was imported, block user and ask to import in a new session $importedVersion = Get-LatestModuleVersion -versions $importedVersions if ([system.version]$importedVersion -ne $importedModule.Version) { Write-Error "A different profile version of module $importedModule is imported in this session. Start a new PowerShell session and retry the operation." -Category InvalidOperation return } } } if ($PSCmdlet.ShouldProcess($module, "Importing module for profile $profile in the current scope")) { Write-Verbose "Importing module $module" Import-Module -Name $Module -RequiredVersion $version -Global } } } } <# .ExternalHelp help\AzureRM.Bootstrapper-help.xml #> function Install-AzureRmProfile { [CmdletBinding(SupportsShouldProcess=$true)] param() DynamicParam { $params = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary Add-ProfileParam $params Add-ScopeParam $params Add-ForceParam $params return $params } PROCESS { $ProfileMap = (Get-AzProfile) $Profile = $PSBoundParameters.Profile $Scope = $PSBoundParameters.Scope $Modules = ($ProfileMap.$Profile | Get-Member -MemberType NoteProperty).Name # If AzureRM $RollUpModule is present in $profile, it will install all the dependent modules; no need to specify other modules if ($Modules.Contains($RollUpModule)) { $Modules = @($RollUpModule) } $PSBoundParameters.Remove('Profile') | Out-Null $PSBoundParameters.Remove('Scope') | Out-Null $ModuleCount = 0 foreach ($Module in $Modules) { $ModuleCount = $ModuleCount + 1 if (Find-PotentialConflict -Module $Module @PSBoundParameters) { continue } $version = Get-AzureRmModule -Profile $Profile -Module $Module if ($null -eq $version) { $versions = $ProfileMap.$Profile.$Module $version = Get-LatestModuleVersion -versions $versions if ($PSCmdlet.ShouldProcess($Module, "Installing Module $Module version: $version")) { Write-Progress -Activity "Installing Module $Module version: $version" -Status "Progress:" -PercentComplete ($ModuleCount/($Modules.Length)*100) Write-Verbose "Installing module $module" Invoke-InstallModule -module $Module -version $version -scope $scope } } } } } <# .ExternalHelp help\AzureRM.Bootstrapper-help.xml #> function Uninstall-AzureRmProfile { [CmdletBinding(SupportsShouldProcess = $true)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidShouldContinueWithoutForce", "")] param() DynamicParam { $params = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary Add-ProfileParam $params Add-ForceParam $params return $params } PROCESS { $ProfileMap = (Get-AzProfile) $Profile = $PSBoundParameters.Profile $Force = $PSBoundParameters.Force if ($PSCmdlet.ShouldProcess("$Profile", "Uninstall Profile")) { if (($Force.IsPresent -or $PSCmdlet.ShouldContinue("Uninstall Profile $Profile", "Removing Modules for profile $Profile"))) { Write-Verbose "Trying to uninstall profile $profile" Uninstall-ProfileHelper -PMap $ProfileMap @PSBoundParameters } } } } <# .ExternalHelp help\AzureRM.Bootstrapper-help.xml #> function Update-AzureRmProfile { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] [CmdletBinding(SupportsShouldProcess = $true)] param() DynamicParam { $params = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary Add-ProfileParam $params Add-ForceParam $params Add-RemoveParam $params Add-ModuleParam $params Add-ScopeParam $params return $params } PROCESS { # Update Profile cache, if not up-to-date $ProfileMap = (Get-AzProfile -Update) $profile = $PSBoundParameters.Profile $Remove = $PSBoundParameters.RemovePreviousVersions $PSBoundParameters.Remove('RemovePreviousVersions') | Out-Null # Install & import the required version Use-AzureRmProfile @PSBoundParameters $PSBoundParameters.Remove('Force') | Out-Null $PSBoundParameters.Remove('Scope') | Out-Null # Remove previous versions of the profile? if ($Remove.IsPresent -and $PSCmdlet.ShouldProcess($profile, "Remove previous versions of profile")) { # Remove-PreviousVersions and clean up cache Update-ProfileHelper @PSBoundParameters -RemovePreviousVersions } } } function Set-BootstrapRepo { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param([string]$Repo) $script:BootStrapRepo = $Repo } <# .ExternalHelp help\AzureRM.Bootstrapper-help.xml #> function Set-AzureRmDefaultProfile { [CmdletBinding(SupportsShouldProcess = $true)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidShouldContinueWithoutForce", "")] param() DynamicParam { $params = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary Add-ProfileParam $params Add-ForceParam $params Add-ScopeParam $params return $params } PROCESS { $armProfile = $PSBoundParameters.Profile $Scope = $PSBoundParameters.Scope $Force = $PSBoundParameters.Force $defaultProfile = $Global:PSDefaultParameterValues["*-AzureRmProfile:Profile"] if ($defaultProfile -ne $armProfile) { if ($PSCmdlet.ShouldProcess("$armProfile", "Set Default Profile")) { if (($Force.IsPresent -or $PSCmdlet.ShouldContinue("Are you sure you would like to set $armProfile as Default Profile?", "Setting $armProfile as Default profile"))) { # Check Profile existence and choose proper profile $profilePath = Select-Profile -Scope $Scope # Set DefaultProfile for this session $Global:PSDefaultParameterValues["*-AzureRmProfile:Profile"]="$armProfile" $Global:PSDefaultParameterValues["Import-Module:RequiredVersion"]={ Get-ModuleVersion -armProfile $Global:PSDefaultParameterValues["*-AzureRmProfile:Profile"] -invocationLine $MyInvocation.Line } # Edit the profile content $profileContent = @" ########## BEGIN AzureRM.Bootstrapper scripts `$PSDefaultParameterValues["*-AzureRmProfile:Profile"]="$armProfile" `r`n "@ # Get Script to be added to the $profile path $profileContent += Get-ScriptBlock -ProfilePath $profilePath Write-Verbose "Updating default profile value to $armProfile" Write-Debug "Removing previous setting if exists" Remove-ProfileSetting -profilePath $profilePath Write-Debug "Adding new default profile value as $armProfile" $AddContentScriptBlock = { Add-Content -Value $profileContent -Path $profilePath -ErrorAction Stop } Invoke-CommandWithRetry -ScriptBlock $AddContentScriptBlock } } } } } <# .ExternalHelp help\AzureRM.Bootstrapper-help.xml #> function Remove-AzureRmDefaultProfile { [CmdletBinding(SupportsShouldProcess = $true)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidShouldContinueWithoutForce", "")] param() DynamicParam { $params = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary Add-ForceParam $params return $params } PROCESS { $Force = $PSBoundParameters.Force if ($PSCmdlet.ShouldProcess("ARM Default Profile", "Remove Default Profile")) { if (($Force.IsPresent -or $PSCmdlet.ShouldContinue("Are you sure you would like to remove Default Profile?", "Remove Default profile"))) { Write-Verbose "Removing default profile value" $Global:PSDefaultParameterValues.Remove("*-AzureRmProfile:Profile") $Global:PSDefaultParameterValues.Remove("Import-Module:RequiredVersion") # Remove AzureRm modules except bootstrapper module $importedModules = Get-Module "Azure*" foreach ($importedModule in $importedModules) { if ($importedModule.Name -eq "AzureRM.Bootstrapper") { continue } Remove-Module -Name $importedModule -Force -ErrorAction "SilentlyContinue" } # Remove content from $profile $profiles = @() if ($script:IsAdmin) { $profiles += $profile.AllUsersAllHosts } $profiles += $profile.CurrentUserAllHosts foreach ($profilePath in $profiles) { if (-not (Test-Path -path $profilePath)) { continue } Remove-ProfileSetting -profilePath $profilePath } } } } } # SIG # Begin signature block # MIIkFwYJKoZIhvcNAQcCoIIkCDCCJAQCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDAx/34tnsp6zdb # rxTiGNyAKuh1nDE3luQLOMWrdQuaoKCCDZMwggYRMIID+aADAgECAhMzAAAAjoeR # pFcaX8o+AAAAAACOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMTYxMTE3MjIwOTIxWhcNMTgwMjE3MjIwOTIxWjCBgzEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UECxMETU9Q # UjEeMBwGA1UEAxMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMIIBIjANBgkqhkiG9w0B # AQEFAAOCAQ8AMIIBCgKCAQEA0IfUQit+ndnGetSiw+MVktJTnZUXyVI2+lS/qxCv # 6cnnzCZTw8Jzv23WAOUA3OlqZzQw9hYXtAGllXyLuaQs5os7efYjDHmP81LfQAEc # wsYDnetZz3Pp2HE5m/DOJVkt0slbCu9+1jIOXXQSBOyeBFOmawJn+E1Zi3fgKyHg # 78CkRRLPA3sDxjnD1CLcVVx3Qv+csuVVZ2i6LXZqf2ZTR9VHCsw43o17lxl9gtAm # +KWO5aHwXmQQ5PnrJ8by4AjQDfJnwNjyL/uJ2hX5rg8+AJcH0Qs+cNR3q3J4QZgH # uBfMorFf7L3zUGej15Tw0otVj1OmlZPmsmbPyTdo5GPHzwIDAQABo4IBgDCCAXww # HwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0OBBYEFKvI1u2y # FdKqjvHM7Ww490VK0Iq7MFIGA1UdEQRLMEmkRzBFMQ0wCwYDVQQLEwRNT1BSMTQw # MgYDVQQFEysyMzAwMTIrYjA1MGM2ZTctNzY0MS00NDFmLWJjNGEtNDM0ODFlNDE1 # ZDA4MB8GA1UdIwQYMBaAFEhuZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEsw # SaBHoEWGQ2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0Nv # ZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsG # AQUFBzAChkVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p # Y0NvZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkq # hkiG9w0BAQsFAAOCAgEARIkCrGlT88S2u9SMYFPnymyoSWlmvqWaQZk62J3SVwJR # avq/m5bbpiZ9CVbo3O0ldXqlR1KoHksWU/PuD5rDBJUpwYKEpFYx/KCKkZW1v1rO # qQEfZEah5srx13R7v5IIUV58MwJeUTub5dguXwJMCZwaQ9px7eTZ56LadCwXreUM # tRj1VAnUvhxzzSB7pPrI29jbOq76kMWjvZVlrkYtVylY1pLwbNpj8Y8zon44dl7d # 8zXtrJo7YoHQThl8SHywC484zC281TllqZXBA+KSybmr0lcKqtxSCy5WJ6PimJdX # jrypWW4kko6C4glzgtk1g8yff9EEjoi44pqDWLDUmuYx+pRHjn2m4k5589jTajMW # UHDxQruYCen/zJVVWwi/klKoCMTx6PH/QNf5mjad/bqQhdJVPlCtRh/vJQy4njpI # BGPveJiiXQMNAtjcIKvmVrXe7xZmw9dVgh5PgnjJnlQaEGC3F6tAE5GusBnBmjOd # 7jJyzWXMT0aYLQ9RYB58+/7b6Ad5B/ehMzj+CZrbj3u2Or2FhrjMvH0BMLd7Hald # G73MTRf3bkcz1UDfasouUbi1uc/DBNM75ePpEIzrp7repC4zaikvFErqHsEiODUF # he/CBAANa8HYlhRIFa9+UrC4YMRStUqCt4UqAEkqJoMnWkHevdVmSbwLnHhwCbww # ggd6MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD # VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe # MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3Nv # ZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5 # MDlaFw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIw # MTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQ # TTS68rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULT # iQ15ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYS # L+erCFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494H # DdVceaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZ # PrGMXeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5 # bmR/U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGS # rhwjp6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADh # vKwCgl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON # 7E1JMKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xc # v3coKPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqw # iBfenk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMC # AQAwHQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQM # HgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1Ud # IwQYMBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0 # dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0Nl # ckF1dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUF # BzAChkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0Nl # ckF1dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGC # Ny4DMIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp # b3BzL2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcA # YQBsAF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZI # hvcNAQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4s # PvjDctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKL # UtCw/WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7 # pKkFDJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft # 0N3zDq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4 # MnEnGn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxv # FX1Fp3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG # 0QaxdR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf # 0AApxbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkY # S//WsyNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrv # QQqxP/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIV # 2jCCFdYCAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAA # AI6HkaRXGl/KPgAAAAAAjjANBglghkgBZQMEAgEFAKCBzDAZBgkqhkiG9w0BCQMx # DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq # hkiG9w0BCQQxIgQgc8mpym9SnCBIfC5C8556xi6ih2huZbpyJa6XtSiqMNwwYAYK # KwYBBAGCNwIBDDFSMFCgNoA0AE0AaQBjAHIAbwBzAG8AZgB0ACAAQQB6AHUAcgBl # ACAAUABvAHcAZQByAFMAaABlAGwAbKEWgBRodHRwOi8vQ29kZVNpZ25JbmZvIDAN # BgkqhkiG9w0BAQEFAASCAQBfm3Gn+yUYMvBi/C3IypMttkGjSq7rK89LeSkK0G3h # LJbKbP5WXL4jjGifFot9zcISZIFS1ACZXtjhJS9w4xJTdAsA65xjqqX6DjX6pAs8 # 9QtEwp/+IHyDe8tJVmcpXZ9T2T38IsLsyn02Rjgk+Gca3esDDeBLbVchWF+kxsj6 # UOQlyoaDfX9iPSEXaxP2e3GjpiB3KuvZYvlKeHg+3bduvNs9aXq6PpvxeSInEuwF # Y2B2UlA9hq7sPdQ2ceBn5R5uvwfGi0013pCegNpA1h/O5QyDHqHkuwLZhPKkRon8 # e3ubh6PZeGAKdlKlaPWstORotS83pO2M8N1i9yBS2RPyoYITRjCCE0IGCisGAQQB # gjcDAwExghMyMIITLgYJKoZIhvcNAQcCoIITHzCCExsCAQMxDzANBglghkgBZQME # AgEFADCCAToGCyqGSIb3DQEJEAEEoIIBKQSCASUwggEhAgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEINAshQO0CA8PIMASWb7ZYbR6GEs7wk7l5c2NxoHt # idkbAgZZVEKiVt0YEzIwMTcwNzA3MDM1NzIzLjk1MVowBIACAfSggbmkgbYwgbMx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDTALBgNVBAsTBE1P # UFIxJzAlBgNVBAsTHm5DaXBoZXIgRFNFIEVTTjoxNDhDLUM0QjktMjA2NjElMCMG # A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCDswwggZxMIIEWaAD # AgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBD # ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0yNTA3 # MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjANBgkq # hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RUENWl # CgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBED/Fg # iIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50YWeR # X4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd/Xcf # PfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaRtogI # Neh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQABo4IB # 5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8RhvF # M2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAP # BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjE # MFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kv # Y3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEF # BQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w # a2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSABAf8E # gZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3dy5t # aWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEFBQcC # AjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBtAGUA # bgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Ehb7Pr # psz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7uVOM # zPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqRUgCv # OA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9Va8v # /rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8+n99 # lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+Y1kl # D3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh2rBQ # Hm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRyzR30 # uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoouLGp # 25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx16HS # xVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341Hgi6 # 2jbb01+P3nSISRIwggTaMIIDwqADAgECAhMzAAAAtEM6HP62ulKJAAAAAAC0MA0G # CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTE2 # MDkwNzE3NTY1OFoXDTE4MDkwNzE3NTY1OFowgbMxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xDTALBgNVBAsTBE1PUFIxJzAlBgNVBAsTHm5DaXBo # ZXIgRFNFIEVTTjoxNDhDLUM0QjktMjA2NjElMCMGA1UEAxMcTWljcm9zb2Z0IFRp # bWUtU3RhbXAgU2VydmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB # AOCBT7u10TA6P4MAt8xWn/ebrLqzXWQRHoLWetfuIPuz0QgBY5dXp8LiTFaotVfb # oMbfs9EYsR5+kawZGVFtc/6eLW0kQiDEyO9GadBvHExhapQzD5Q+26JvJSq/GdLO # AzpSFYBFZRv9z7X/N7JoZpllre7/voFNQ54LeAnWKo89irQHM9mwUCRR73/1JNQV # DI92GNhWvLVtoyl02HUHSEAZjmYYkQICcBtB2/knoRl1xpao+kEvu3rFNKYwoJeT # xxpsAFqCtowAZ5LLzE3SbKnjxF55CwGVrqikuSL++UVeP30ZG2GcN3TZo1HF7kjZ # VDMUg1+y5w97sB/Jl76ZhXECAwEAAaOCARswggEXMB0GA1UdDgQWBBQMk6tF1GwL # dy/zK1fz6STEhwf0XzAfBgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBW # BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUH # AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp # L2NlcnRzL01pY1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAA # MBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQACtWFboKfS # aqwmL7bCNKN0+6h7kLGCLaiJEl2k9oILWT0n53odeSz4y3LPR2vvb97rkm3ur/te # oZGEeONfUXvElzHgUI3tupn3iCq/UWriI+Q3R91m4sWFCOz+dXHrem1JiSUhHZ2O # FuRYpOaolbWbtJOhiiy4nCE6vAFRH5jgn/Pt6lDMuzeEKWrwImylT64TEXlmZHAn # owz6tN5Mu1B+xJ9/6YFRBN6uFF/mDgwQgbpGrDtiEue10Qow8NMp7PrTcDFuCnok # UcIi5g4it0NRqlNVIvUktefTdXgpoTpbaQrgrXyZz5+0kigHfqpwxDKGW0oO2ES2 # 9T5nH3nD8itToYIDdTCCAl0CAQEwgeOhgbmkgbYwgbMxCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xDTALBgNVBAsTBE1PUFIxJzAlBgNVBAsTHm5D # aXBoZXIgRFNFIEVTTjoxNDhDLUM0QjktMjA2NjElMCMGA1UEAxMcTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgU2VydmljZaIlCgEBMAkGBSsOAwIaBQADFQAHwJWXri5ObsnT # dfbZiVGYiyBtOqCBwjCBv6SBvDCBuTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBOVFMg # RVNOOjU3RjYtQzFFMC01NTRDMSswKQYDVQQDEyJNaWNyb3NvZnQgVGltZSBTb3Vy # Y2UgTWFzdGVyIENsb2NrMA0GCSqGSIb3DQEBBQUAAgUA3QjqVzAiGA8yMDE3MDcw # NjE2NTY1NVoYDzIwMTcwNzA3MTY1NjU1WjBzMDkGCisGAQQBhFkKBAExKzApMAoC # BQDdCOpXAgEAMAYCAQACAVAwBwIBAAICIqcwCgIFAN0KO9cCAQAwNgYKKwYBBAGE # WQoEAjEoMCYwDAYKKwYBBAGEWQoDAaAKMAgCAQACAwehIKEKMAgCAQACAwehIDAN # BgkqhkiG9w0BAQUFAAOCAQEABMItGo7IZ0K9uDuxPMxfe/jAEyt1qzA+tM7hPLyL # 2YO3l81SQURIDZ/EPB7yAMuvdNlYnRkqEquGGj2q5ixkdXYwv/uT4zDAHaVcdxa5 # XwIHSaEtKSLusJjkVeU1oUtxVsPGja8zvOl79Hq0qnSwWoaAR82qrFv/C+HLFN1g # o6p6XL8WzhBwOVdhDzkYdQT2GV/PQ/FKa1qPYC3hA+ljHJr7U5ByYNhbYRfC3Fhj # oS4fTsGL5WJltF9ea2AyYH0TvKLqC/ptplWtavnL0iz/g84wAGXMz0e1V1+9DnW7 # 5mITnXNNGfBLW4+veObg+/EHnuHSaSZe3cLagaY5h5aF2DGCAvUwggLxAgEBMIGT # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAAtEM6HP62ulKJAAAA # AAC0MA0GCWCGSAFlAwQCAQUAoIIBMjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQ # AQQwLwYJKoZIhvcNAQkEMSIEIKWqM+vzWhvEyIC1lc+TK+aBOIJsb+9xGj0E66XS # 9iE0MIHiBgsqhkiG9w0BCRACDDGB0jCBzzCBzDCBsQQUB8CVl64uTm7J03X22YlR # mIsgbTowgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA # ALRDOhz+trpSiQAAAAAAtDAWBBSe3AwkfmU9QXe0oLThJu+uOetrIDANBgkqhkiG # 9w0BAQsFAASCAQCnaEOSEttX6odid7tpG7kC4RF7+x+tgrfkT6e5EVEKE/Q9v7+Y # Qlaz81pH40BwIpD940WEtWNvG0Zf9B+OV2ulLt/KGWdMPL8kCx0WG7g6CJ+xPuH1 # +y7mN9+AG3zFjYWlJb311uWB73WuEb8iWUGrpZJLyuymcmLUELWnE8c9/zHvGQQo # 9cO7zyG5bg+ozeqGiBbO9vkuqnDmh9iPdjkXg7N9Uio7CWyu6wUMIk6JNoMnOHdX # gflQ/z93Iq4Y7iPJzwUv1dp9biKOtITQc8rRo8W6IC2biAYpUGGV1EVuMSpfiHHa # DUmWCE9KtLH1yyDsPqroVP1ufIn06vtfRMyC # SIG # End signature block |