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)) { If (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { $script:IsAdmin = $true } } else { # 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 { $retryCount = 0 Do { $retryCount = $retryCount + 1 try { $WebResponse = Invoke-WebRequest -uri $PSProfileMapEndpoint -ErrorVariable RestError $Status = "success" } catch { if ($retryCount -le 3) { Start-Sleep -Seconds 3 } else { throw $_ } } } while ($Status -ne "success") return $WebResponse } # Get-Content with retry logic; to handle parallel requests function RetryGetContent { param([string]$FilePath) $retryCount = 0 Do { $retryCount = $retryCount + 1 try { $ProfileMap = Get-Content -Raw -Path $FilePath -ErrorAction stop | ConvertFrom-Json $Status = "success" } catch { Start-Sleep -Seconds 3 } } while (($Status -ne "success") -and ($retryCount -lt 3)) return $ProfileMap } # 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)) { $ProfileMap = RetryGetContent -FilePath $script:LatestProfileMapPath.FullName 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 # Update $script:LatestProfileMapPath $script:LatestProfileMapPath = Get-ChildItem $ProfileCache | Where-Object { $_.FullName.equals($CacheFilePath)} return $OnlineProfileMap } # Helper to retrieve profile map from http response function RetrieveProfileMap { param($WebResponse) $OnlineProfileMap = ($WebResponse.Content -replace "`n|`r|`t") | 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)) { $ProfileMap = RetryGetContent -FilePath $script:LatestProfileMapPath.FullName if ($null -ne $ProfileMap) { return $ProfileMap } } # If cache doesn't exist, Check embedded source $defaults = [System.IO.Path]::GetDirectoryName($PSCommandPath) $ProfileMap = RetryGetContent -FilePath (Join-Path -Path $defaults -ChildPath "ProfileMap.json") 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 available for install from gallery function Get-ProfilesAvailable { param([parameter(Mandatory = $true)] [PSCustomObject] $ProfileMap) $ProfileList = "" foreach ($Profile in $ProfileMap) { foreach ($Module in ($Profile | get-member -MemberType NoteProperty).Name) { $ProfileList += "Profile: $Module`n" $ProfileList += "----------------`n" $ProfileList += ($Profile.$Module | Format-List | Out-String) } } return $ProfileList } # 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)) { $result[$key] += @{$module = $ProfileMap.$Key.$module} } else { $result.Add($key, @{$module = $ProfileMap.$Key.$module}) } } } } # 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 } # Function to remove Previous profile map function Remove-ProfileMapFile { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param([string]$ProfileMapPath) if (Test-Path -Path $ProfileMapPath) { RemoveWithRetry -Path $ProfileMapPath -Force } } # Remove-Item command with Retry function RemoveWithRetry { param ([string]$Path) $retries = 3 $secondsDelay = 2 $retrycount = 0 $completedSuccessfully = $false while (-not $completedSuccessfully) { try { Remove-Item @PSBoundParameters -ErrorAction Stop $completedSuccessfully = $true } catch { if ($retrycount -ge $retries) { throw } else { Start-Sleep $secondsDelay $retrycount++ } } } } # Get profiles installed associated with the module version function Test-ProfilesInstalled { param([String]$Module, [String]$Profile, [PSObject]$PMap, [hashtable]$AllProfilesInstalled) # Profiles associated with the particular module version - installed? $profilesAssociated = @() $versionList = $PMap.$Profile.$Module foreach ($version in $versionList) { 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("Remove module $Module version $version", "Removing 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 { 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) { if ($Force.IsPresent) { Invoke-UninstallModule -PMap $PMap -Profile $Profile -Module $module -AllProfilesInstalled $AllProfilesInstalled -RemovePreviousVersions } else { Invoke-UninstallModule -PMap $PMap -Profile $Profile -Module $module -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, [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 -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 module $versionList = $PMap.$Profile.$module foreach ($version in $versionList) { Uninstall-ModuleHelper -version $version @PSBoundParameters } } # Helps to uninstall previous versions of modules in the profile function Remove-PreviousVersion { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param([PSObject]$PreviousMap, [PSObject]$LatestMap, [hashtable]$AllProfilesInstalled, [String]$Profile, [Array]$Module, [Switch]$RemovePreviousVersions) $Remove = $PSBoundParameters.RemovePreviousVersions $Modules = $PSBoundParameters.Module $PreviousProfiles = ($PreviousMap | Get-Member -MemberType NoteProperty).Name Write-Verbose "Checking if previous versions of modules are installed" # If the profile was not in $PreviousProfiles, return if($Profile -notin $PreviousProfiles) { return } if ($null -eq $Modules) { $Modules = ($PreviousMap.$Profile | Get-Member -MemberType NoteProperty).Name } foreach ($module in $Modules) { # If the latest version is same as the previous version, do not uninstall. if (($PreviousMap.$Profile.$module | Out-String) -eq ($LatestMap.$Profile.$module | Out-String)) { continue } # Is that module installed? If not skip $versionList = $PreviousMap.$Profile.$module foreach ($version in $versionList) { 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..." # Modules are different. Uninstall previous version. if ($Remove.IsPresent) { Invoke-UninstallModule -PMap $PreviousMap -Profile $Profile -Module $module -AllProfilesInstalled $AllProfilesInstalled -RemovePreviousVersions } else { Invoke-UninstallModule -PMap $PreviousMap -Profile $Profile -Module $module -AllProfilesInstalled $AllProfilesInstalled } } # Uninstall removes module from session; import latest version again $versions = $LatestMap.$Profile.$module $versionEnum = $versions.GetEnumerator() $toss = $versionEnum.MoveNext() $version = $versionEnum.Current Import-Module $Module -RequiredVersion $version -Global } } # Gets profiles installed from all the profilemaps from cache function Get-AllProfilesInstalled { $AllProfilesInstalled = @{} $ProfileCache = Get-ProfileCachePath $ProfileMapHashes = Get-ChildItem $ProfileCache foreach ($ProfileMapHash in $ProfileMapHashes) { $ProfileMap = RetryGetContent -FilePath (Join-Path $ProfileCache $ProfileMapHash.Name) $profilesInstalled = (Get-ProfilesInstalled -ProfileMap $ProfileMap) foreach ($Profile in $profilesInstalled.Keys) { foreach ($Module in ($ProfileMap.$Profile | Get-Member -MemberType NoteProperty).Name) { $versionList = $ProfileMap.$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, if none of the old hash profiles are installed function Update-ProfileHelper { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param([String]$Profile, [Array]$Module, [Switch]$RemovePreviousVersions) Write-Verbose "Attempting to clean up previous versions" # Get all the hash files (ProfileMaps) from cache $ProfileCache = Get-ProfileCachePath $ProfileMapHashes = Get-ChildItem $ProfileCache $LatestProfileMap = RetryGetContent -FilePath $script:LatestProfileMapPath.FullName # Get-Profiles installed across all hashes. $AllProfilesInstalled = Get-AllProfilesInstalled foreach ($ProfileMapHash in $ProfileMapHashes) { # Set flag to delete hash $deleteHash = $true # Do not process the latest hash; we don't want to remove the latest hash if ($ProfileMapHash.Name -eq $script:LatestProfileMapPath.Name) { continue } # Compare previous & latest map for the update profile. Uninstall previous if they are different $previousProfileMap = RetryGetContent -FilePath (Join-Path $profilecache $ProfileMapHash) Remove-PreviousVersion -PreviousMap $previousProfileMap -LatestMap $LatestProfileMap -AllProfilesInstalled $AllProfilesInstalled @PSBoundParameters # If the previous map has profiles installed, do not delete it. $profilesInstalled = (Get-ProfilesInstalled -ProfileMap $previousProfileMap) foreach ($PreviousProfile in $profilesInstalled.Keys) { # Map can be deleted if the only profile installed is the updated one. if ($PreviousProfile -eq $Profile) { continue } else { $deleteHash = $false } } # If none were installed, remove the hash if ($deleteHash -ne $false) { Write-Verbose "Cleaning up cache" Remove-ProfileMapFile -ProfileMapPath (Join-Path $profilecache $ProfileMapHash) } } } # 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($env:HOMEPATH)) { $IsPotentialConflict = $true } } } else { $availableModules | ForEach-Object { if (($null -ne $_.Path) -and $_.Path.Contains($env:ProgramFiles)) { $IsPotentialConflict = $true } } } 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 } } 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 } } } # 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 = 1 $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 $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" Get-ProfilesAvailable $ProfileMap } 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) $Output = @() foreach ($key in $profilesInstalled.Keys) { $Output += "Profile : $key" $Output += "-----------------" $Output += ($profilesInstalled.$key | Format-Table -HideTableHeaders | Out-String) } if ($IncompleteProfiles.Count -gt 0) { Write-Warning "Some modules from profile(s) $(@($IncompleteProfiles) -join ', ') were not installed. Use Install-AzureRmProfile to install missing modules." } $Output } } } <# .ExternalHelp help\AzureRM.Bootstrapper-help.xml #> function Use-AzureRmProfile { [CmdletBinding(SupportsShouldProcess=$true)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] [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 if (Find-PotentialConflict -Module $Module @PSBoundParameters) { continue } $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"))) { $versions = $ProfileMap.$Profile.$Module $versionEnum = $versions.GetEnumerator() $toss = $versionEnum.MoveNext() $version = $versionEnum.Current 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 } } # Block user if they try to import a module to the session where a different version of the same module is already imported if ($null -ne (Get-Module -Name $Module | Where-Object { $_.Version -ne $version} )) { Write-Error "A different version of module $module is already imported in this session. Start a new PowerShell session and retry the operation." 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()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] 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 $versionEnum = $versions.GetEnumerator() $toss = $versionEnum.MoveNext() $version = $versionEnum.Current 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 } # SIG # Begin signature block # MIIkGwYJKoZIhvcNAQcCoIIkDDCCJAgCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB/0liwKKlI1nlN # aJqGK5EdUhm1Ik0gDdvcWyxN/UWIc6CCDZMwggYRMIID+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 # 3jCCFdoCAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAA # AI6HkaRXGl/KPgAAAAAAjjANBglghkgBZQMEAgEFAKCBzDAZBgkqhkiG9w0BCQMx # DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq # hkiG9w0BCQQxIgQgfQzuIW5RG2FoeUqq6BVRutu1xFbDVILBU5X8juy/sbMwYAYK # KwYBBAGCNwIBDDFSMFCgNoA0AE0AaQBjAHIAbwBzAG8AZgB0ACAAQQB6AHUAcgBl # ACAAUABvAHcAZQByAFMAaABlAGwAbKEWgBRodHRwOi8vQ29kZVNpZ25JbmZvIDAN # BgkqhkiG9w0BAQEFAASCAQA7AFSBOQ0M5drxebgd2tF4PS5u1iECumj4C1TF/oXs # fti+cqTWUSkevOuaXqRCgb39KpSMrgozVvYO07RgJ47Yg5KCFbdYNaQkiNkZH/+o # 6PKjZnLVeX0EpXmNOGshN/cdba0ZaMDcEz6hxRtkcMtatXw0BhTx3Mr0/JwNug/v # Vd+SijDtWQx1E579QwhsK/BT3eDOOxnPL4rVwsDy1PeZGfaDyUGfSFTe6Je3KRqA # mb0THIjhHhx12UOcW6EZVlGRI4b/21EVoBW/aflbz9ghxTamErOhgXUmB+ufKrqV # 9f5VnOlFY2cUuP7Mi0SMPVMptSnPOKIG3aGFFH1Ork/WoYITSjCCE0YGCisGAQQB # gjcDAwExghM2MIITMgYJKoZIhvcNAQcCoIITIzCCEx8CAQMxDzANBglghkgBZQME # AgEFADCCAT0GCyqGSIb3DQEJEAEEoIIBLASCASgwggEkAgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEICBNUHI6J+jb42j6qiIUmEKpTqGx9a9CrqpunQWU # 5qsVAgZYr7LWvykYEzIwMTcwMzE0MDExMTMyLjA5MVowBwIBAYACAfSggbmkgbYw # gbMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDTALBgNVBAsT # BE1PUFIxJzAlBgNVBAsTHm5DaXBoZXIgRFNFIEVTTjpCOEVDLTMwQTQtNzE0NDEl # MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCDs0wggZxMIIE # WaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v # dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0y # NTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjAN # BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RU # ENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBE # D/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50 # YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd # /XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaR # togINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQAB # o4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8 # RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIB # hjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fO # mhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w # a2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggr # BgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv # bS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSAB # Af8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEF # BQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBt # AGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Eh # b7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7 # uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqR # UgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9 # Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8 # +n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+ # Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh # 2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRy # zR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoo # uLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx # 16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341 # Hgi62jbb01+P3nSISRIwggTaMIIDwqADAgECAhMzAAAAn2fytagjBlt7AAAAAACf # MA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n # dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y # YXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4X # DTE2MDkwNzE3NTY0N1oXDTE4MDkwNzE3NTY0N1owgbMxCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xDTALBgNVBAsTBE1PUFIxJzAlBgNVBAsTHm5D # aXBoZXIgRFNFIEVTTjpCOEVDLTMwQTQtNzE0NDElMCMGA1UEAxMcTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgU2VydmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC # ggEBALkI8SOc3cQCLwKFoaMnl2T5A5wSVD9Tglq4Put9bhjFcsEn1XApDPCWS9aP # hMcWOWKe+7ENI4Si4zD30nVQC9PZ0NDu+pK9XV83OfjGchFkKzOBRddOhpsQkxFg # MF3RfLTNXAEqffnNaReXwtVUkiGEJvW6KmABixzP0aeUVmJ6MHnJnmo+TKZdoVl7 # cg6TY6LCoze/F6rhOXmi/P3X/K3jHtmAaxL9Ou53jjDgO5Rjxt6ZEamdEsGF2SWZ # 6wH6Dmg9G6iZPxgw+mjODwReL6jwh7H2XhsvzoFMrSERMzIIf2eJGAM9C0GR0BZH # yRti17QqL5TaCuWPjMxTKXX4DlkCAwEAAaOCARswggEXMB0GA1UdDgQWBBT9ixsi # w30jR3amHt/gZtRS6bb5oDAfBgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVt # VTBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtp # L2NybC9wcm9kdWN0cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYB # BQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v # cGtpL2NlcnRzL01pY1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8E # AjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBlEMFs # a88VHq8PSDbr3y0LvAAA5pFmGlCWZbkxD2WMqfF0y8fnlvgb874z8sz8QZzByCmY # 1jHyHTc98Zekz7L2Y5SANUIa8jyU36c64Ck5fY6Pe9hUA1RG/1zP+eq080chUPCF # 2zezhfwuz9Ob0obO64BwW0GZgYYz1hjsq+DBkSCBRV59ryFpzgKRwhWF8quXtHDp # imiJx+ds2VZSwEVk/QRY7pLuUvedN8P5DNuLaaRw3oJcs2Wxh2jWS5T8Y3JevUo3 # K3VTtHPi2IBWISkEG7TOnNEUcUXDMGSOeZ27kuPFzKkDVbtzvwEVepkGrsZ1W+1x # uDYPQ1b3BMG8C79HoYIDdjCCAl4CAQEwgeOhgbmkgbYwgbMxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDTALBgNVBAsTBE1PUFIxJzAlBgNVBAsT # Hm5DaXBoZXIgRFNFIEVTTjpCOEVDLTMwQTQtNzE0NDElMCMGA1UEAxMcTWljcm9z # b2Z0IFRpbWUtU3RhbXAgU2VydmljZaIlCgEBMAkGBSsOAwIaBQADFQBs0ycI8vnZ # qMv5Gd6SS0qt2xmjwaCBwjCBv6SBvDCBuTELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBO # VFMgRVNOOjU3RjYtQzFFMC01NTRDMSswKQYDVQQDEyJNaWNyb3NvZnQgVGltZSBT # b3VyY2UgTWFzdGVyIENsb2NrMA0GCSqGSIb3DQEBBQUAAgUA3HG2hDAiGA8yMDE3 # MDMxNDAwMjMzMloYDzIwMTcwMzE1MDAyMzMyWjB0MDoGCisGAQQBhFkKBAExLDAq # MAoCBQDccbaEAgEAMAcCAQACAgHgMAcCAQACAhjiMAoCBQDccwgEAgEAMDYGCisG # AQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwGgCjAIAgEAAgMW42ChCjAIAgEAAgMH # oSAwDQYJKoZIhvcNAQEFBQADggEBABtn9P7C84fsSh4DE34mW3y1R7gHtZKM56zC # MJyn4gQiNTI8hwHbMlbHay6IfIK76OckXh5m21yg/YNydUMFpZbd5JydrVCCUoZC # 3BNu7B74vw6EQEZzxy/4XQh5CT9+oelzNfuxSoFZi7Kmwd5pvHbAkkrXu9bNoVg5 # Kt8uxeUHmRGYFIlUIp/mKRosWOTS3/48nQ3APdQC/GS5CNJU6t0FEVwRX65lr6+2 # ysP1n5h79j8bP86DJOtvB4Qqb2mDwZOl4H/KmvYpwdcvhJ1NoeA7l9cIPWjIz7Hd # 6CZKBYH5THtUjw4kwrmvOEPmNPRceSRCnR/G1rXS3jI4x9SStQwxggL1MIIC8QIB # ATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAJ9n8rWoIwZb # ewAAAAAAnzANBglghkgBZQMEAgEFAKCCATIwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3 # DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCDGR5InNcY0fLZ2P9mV60dGksBmdydMAqRx # zJzzd0FucTCB4gYLKoZIhvcNAQkQAgwxgdIwgc8wgcwwgbEEFGzTJwjy+dmoy/kZ # 3pJLSq3bGaPBMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAC # EzMAAACfZ/K1qCMGW3sAAAAAAJ8wFgQU6iI0UNHA7VG5/D1DM/22WW7XeqkwDQYJ # KoZIhvcNAQELBQAEggEAikKeGnHdZt6Yd3nUiOVdkWaWys5ee9vOod5XcN5OyB5C # f+s22E9hzEHHRAienv/Iep8fw7s8CnF0vxiMJnfCal3vWdBi/DknjbNCLTbRW1er # tQl7sKX95KYUMl9ykn4bs6+IqJCY6R9cfpydNxibtUXUQQ1wdWwE5sgfL8SD//L8 # bcp7m8pFGm9lX5t8H/l6QhgtO0STQvMsUGEYOycpajuZeclSYk7dEA+7tRzuKDbY # ohNSaIdyYj6P3UoV4fpGaID5983touW8woF7BF79Dc5vBIMSb3AjGxRA7RtCqE1V # TMGUunAyz5vhwV7nBuUpRn3fPEVKt3+RDP1YpeDMKQ== # SIG # End signature block |