CredExtra.psm1
# module variables $ScriptPath = Split-Path (Get-Variable MyInvocation -Scope Script).Value.Mycommand.Definition -Parent $ModuleName = (Get-Item (Get-Variable MyInvocation -Scope Script).Value.Mycommand.Definition).BaseName # turn on informational messages $InformationPreference = 'Continue' # load localized language Import-LocalizedData -BindingVariable 'Messages' -FileName 'Messages' -BaseDirectory (Join-Path $ScriptPath 'lang') # enum for username translation context enum TranslateContext { Domain = 1 #ADS_NAME_INITTYPE_DOMAIN Server = 2 #ADS_NAME_INITTYPE_SERVER GlobalCatalog = 3 #ADS_NAME_INITTYPE_GC } # enum for username translation types enum TranslateType { DistinguishedName = 1 #ADS_NAME_TYPE_1779 CanonicalName = 2 #ADS_NAME_TYPE_CANONICAL NTAccount = 3 #ADS_NAME_TYPE_NT4 DisplayName = 4 #ADS_NAME_TYPE_DISPLAY DomainSimple = 5 #ADS_NAME_TYPE_DOMAIN_SIMPLE EnterpriseSimple = 6 #ADS_NAME_TYPE_ENTERPRISE_SIMPLE GUID = 7 #ADS_NAME_TYPE_GUID Unknown = 8 #ADS_NAME_TYPE_UNKNOWN UserPrincipalName = 9 #ADS_NAME_TYPE_USER_PRINCIPAL_NAME CanonicalEx = 10 #ADS_NAME_TYPE_CANONICAL_EX ServicePrincipalName = 11 #ADS_NAME_TYPE_SERVICE_PRINCIPAL_NAME SID = 12 #ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME } <# .SYNOPSIS Caches an encrypted credential file on the local disk that is tied to the current user on the current machine. .PARAMETER Credential The credential to cache. .PARAMETER UserName The user name to cache. .PARAMETER Password The secure string password to cache. .PARAMETER Prompt Prompt for the credential to store. .PARAMETER Force Overwrite an existing credential. .PARAMETER CacheFolder Where the credentials should be stored. .EXAMPLE Add-CredentialToCache -Username DOMAIN\User .EXAMPLE Add-CredentialToCache -Username user@domain.local #> Function Add-CredentialToCache { [CmdletBinding(DefaultParameterSetName='PromptForCredential', SupportsShouldProcess)] Param( [Parameter(ParameterSetName='UsingCredential', Mandatory=$true)] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter(Position=1, ParameterSetName='UsingUserNameAndPassword', Mandatory=$true)] [ValidateNotNull()] [string] $UserName, [Parameter(ParameterSetName='UsingUserNameAndPassword')] [ValidateNotNull()] [System.Security.SecureString] $Password, [Parameter(ParameterSetName='PromptForCredential')] [switch] $Prompt, [switch] $Force, [switch] $PassThru, [System.IO.DirectoryInfo] $CacheFolder = ( '{0}\PowerShell\{1}\{2}' -f [environment]::GetFolderPath('LocalApplicationData'), 'brooksworks.com', 'CredExtra' ) ) process { # build the credential switch ( $PSCmdlet.ParameterSetName ) { 'UsingUserNameAndPassword' { # prompt for password if not provided if ( $null -eq $Password ) { $Password = Read-Host -Prompt $Messages.PasswordPrompt -AsSecureString } # build a credential object $Credential = New-Object System.Management.Automation.PSCredential( $UserName, $Password ) } 'PromptForCredential' { # prompt the user for a credential $Credential = Get-Credential } } # destination file name $FileName = $Credential.UserName + '.xml' # destination file path [System.IO.FileInfo]$FilePath = Join-Path $CacheFolder $FileName # verify any domain or machine sub-directory exists if ( -not(Test-Path -Path $FilePath.Directory.FullName -PathType Container) ) { New-Item -Path $FilePath.Directory -ItemType Directory -Force -Confirm:$false > $null } # validate output file #if ( $PSCmdlet. if ( -not($Force) -and (Test-Path -Path $FilePath) ) { Write-Error $Messages.CacheOverwriteError } # output credential xml $Credential | Export-Clixml -Path $FilePath -Force # if we passthru pass the credential if ( $PassThru ) { $Credential } } } <# .SYNOPSIS Retrieve a credential from a local cache. .PARAMETER Username The username to retrieve the credential for. .PARAMETER Path Where to get the credential. .PARAMETER UpnSuffix Replaces the UPN suffix portion with the specified UPN suffix. .PARAMETER Domain Replaces the domain portion with the specified domain. .PARAMETER ExcludeDomain Return a credential object that excludes the domain portion of the credential. .OUTPUTS Returns a credential object. .EXAMPLE Get-CredentialToCache -Username DOMAIN\User .EXAMPLE Get-CredentialToCache -Username user@domain.local #> function Get-CredentialFromCache { [CmdletBinding( DefaultParameterSetName='ByUserName' )] Param( [Parameter( Mandatory, Position=1, ParameterSetName='ByUserName' )] [ArgumentCompleter({ param( $CommandName, $ParameterName, $WordToComplete, $CommandAst, $FakeBoundParameters ) $CacheFolder = '{0}\PowerShell\{1}\{2}' -f [environment]::GetFolderPath('LocalApplicationData'), 'brooksworks.com', 'CredExtra' $CacheFolderRegex = [regex]::Escape( $CacheFolder ) Get-ChildItem -Path $CacheFolder -Filter '*.xml' -Recurse | ForEach-Object { $_.FullName -replace "^$CacheFolderRegex\\(.*)\.xml$", '$1' } | Where-Object { $_ -like "$WordToComplete*" } })] [SupportsWildcards()] [Alias('Filter')] [string] $UserName, [Parameter( Mandatory, Position=1, ParameterSetName='ByPath' )] [System.IO.FileInfo] $Path, [switch] $ExcludeDomain, [Parameter( Mandatory, ParameterSetName='List' )] [switch] $List, [TranslateType] $OutputType, [TranslateContext] $Context = 'GlobalCatalog', [System.IO.DirectoryInfo] $CacheFolder = ( '{0}\PowerShell\{1}\{2}' -f [environment]::GetFolderPath('LocalApplicationData'), 'brooksworks.com', 'CredExtra' ) ) process { $CacheFolderRegex = [regex]::Escape( $CacheFolder ) # build the actual path to the credential file switch ($PSCmdlet.ParameterSetName ) { 'ByPath' { $FilePath = $PSBoundParameters.Path } 'ByUserName' { $FilePath = Get-ChildItem -Path $CacheFolder -Filter '*.xml' -Recurse | Where-Object { ( $_.FullName -replace "^$CacheFolderRegex\\(.*)\.xml$", '$1' ) -like "$UserName" } | Select-Object -ExpandProperty FullName } 'List' { Get-ChildItem -Path $CacheFolder -Filter '*.xml' -Recurse | ForEach-Object { $_.FullName -replace "^$CacheFolderRegex\\(.*)\.xml$", '$1' } return } } foreach ( $FilePathItem in $FilePath ) { # if there is no cached credential we throw an error if ( -not (Test-Path -Path $FilePathItem -PathType Leaf ) ) { Write-Error $Messages.CacheMissError } # fetch the credential object $CacheCredential = Import-Clixml -Path $FilePathItem # if the -OutputType is specified we translate the username and return if ( $OutputType ) { $TranslatedUserName = _ConvertUserNameFormat -UserName $CacheCredential.UserName -OutputType $OutputType -Context $Context New-Object System.Management.Automation.PSCredential( $TranslatedUserName, $CacheCredential.Password ) # if the -ExcludeDomain param is included we rebuild the credential without the domain } elseif ( $PSBoundParameters.ExcludeDomain ) { New-Object System.Management.Automation.PSCredential($CacheCredential.GetNetworkCredential().UserName, $CacheCredential.Password) # otherwise we return the cached credential as-is } else { $CacheCredential } } } } <# .SYNOPSIS Creates a credential from a plaintext string. .PARAMETER Username The username to create the credential from. .PARAMETER Password The plaintext password to create the credential from. .OUTPUTS Returns a credential object. .EXAMPLE $cred = Get-CredentialFromPlaintext -Username DOMAIN\User -Password "P@ssword" .EXAMPLE $cred = Get-CredentialToCache -Username user@domain.local -Password "P@ssword" #> Function Get-CredentialFromPlaintext { [CmdletBinding(SupportsShouldProcess=$true)] Param( [Parameter(Mandatory=$true, Position=1, ValueFromPipeline=$true)] [Alias('User','Email','sAMAccountName')] [ValidateNotNullOrEmpty()] [string] $UserName, [Parameter(Mandatory=$true, Position=2, ValueFromPipeline=$true)] [ValidateNotNullOrEmpty()] $Password ) process { $Password = $Password | ConvertTo-SecureString -AsPlainText -Force New-Object System.Management.Automation.PSCredential($UserName, $Password) } } <# .SYNOPSIS Tests a credential to make sure it is valid. .PARAMETER Credential The credential to test. .PARAMETER Domain Switch to indicate that the credential is a domain credential. #> Function Test-Credential { [CmdletBinding(DefaultParameterSetName='MachineContext')] Param( [Parameter(Mandatory=$true)] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter(ParameterSetName='DomainContext')] [switch] $Domain ) begin { Add-Type -AssemblyName System.DirectoryServices.AccountManagement } process { $UserName = $Credential.GetNetworkCredential().UserName $Password = $Credential.GetNetworkCredential().Password $DomainName = $Credential.GetNetworkCredential().Domain if ( $PSCmdlet.ParameterSetName -eq 'DomainContext' ) { Write-Verbose ( $Messages.ValidateUserOnDomainVerboseMessage -f $UserName, $DomainName ) $AuthObj = New-Object System.DirectoryServices.AccountManagement.PrincipalContext( 'Domain', $DomainName ) } elseif ( $PSCmdlet.ParameterSetName -eq 'MachineContext' -and -not( [string]::IsNullOrEmpty( $DomainName ) ) ) { Write-Verbose ( $Messages.ValidateUserOnMachineVerboseMessage -f $UserName, $Domain ) $AuthObj = New-Object System.DirectoryServices.AccountManagement.PrincipalContext( 'Machine', $DomainName ) } else { Write-Verbose ( $Messages.ValidateUserOnMachineVerboseMessage -f $UserName, $env:COMPUTERNAME ) $AuthObj = New-Object System.DirectoryServices.AccountManagement.PrincipalContext( 'Machine', $env:COMPUTERNAME ) } $AuthObj.ValidateCredentials( $UserName, $Password, [System.DirectoryServices.AccountManagement.ContextOptions]::Negotiate ) } } <# .SYNOPSIS Tests that a credential is available in the cache. .PARAMETER UserName The user name to check. #> function Test-CredentialCached { param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $UserName, [System.IO.DirectoryInfo] $CacheFolder = ( '{0}\PowerShell\{1}\{2}' -f [environment]::GetFolderPath('LocalApplicationData'), 'brooksworks.com', 'CredExtra' ) ) $CredentialFile = Join-Path $CacheFolder ( '{0}.xml' -f $UserName ) Test-Path -Path $CredentialFile } <# .SYNOPSIS Converts user names between formats. .DESCRIPTION Converts user names between formats. Uses the ComObject NameTranslate. .PARAMETER UserName The user(s) to convert. .PARAMETER InputType Supports: - Unknown (default) - DistinguishedName - CanonicalName - NTAccount - DisplayName - DomainSimple - EnterpriseSimple - GUID - UserPrincipalName - CanonicalEx - ServicePrincipalName - SID .PARAMETER OutputType Supports: - DistinguishedName - CanonicalName - NTAccount - DisplayName - DomainSimple - EnterpriseSimple - GUID - UserPrincipalName - CanonicalEx - ServicePrincipalName - SID .PARAMETER Credential Credential used for binding to domain. #> function _ConvertUserNameFormat { [CmdletBinding()] param ( [Parameter(Mandatory, Position=0)] [string[]] $UserName, [TranslateType] $InputType = 'Unknown', [Parameter(Mandatory)] [ValidateSet( 'DistinguishedName', 'CanonicalName', 'NTAccount', 'DisplayName', 'DomainSimple', 'EnterpriseSimple', 'GUID', 'UserPrincipalName', 'CanonicalEx', 'ServicePrincipalName', 'SID' )] [TranslateType] $OutputType, [TranslateContext] $Context = 'GlobalCatalog', [pscredential] $Credential ) $NameTranslateComObject = New-Object -ComObject 'NameTranslate' $NameTranslateType = $NameTranslateComObject.GetType() # if a credential is supplied we use the InitEx method if ( $Credential ) { $NameTranslateType.InvokeMember( 'InitEx', 'InvokeMethod', $null, $NameTranslateComObject, ( $Context, $null, $Credential.GetNetworkCredential().UserName, $Credential.GetNetworkCredential().Domain, $Credential.GetNetworkCredential().Password ) ) > $null # otherwise just init with the default user context } else { $NameTranslateType.InvokeMember( 'Init', 'InvokeMethod', $null, $NameTranslateComObject, ( $Context, $null ) ) > $null } $UserName | ForEach-Object { # set the current user name $NameTranslateType.InvokeMember( 'Set', 'InvokeMethod', $null, $NameTranslateComObject, ( $InputType, [string]$_ ) ) > $null # if output type is SID we have to do extra conversion if ( $OutputType -eq [TranslateType]::SID ) { [System.Security.Principal.NTAccount]$NTAccount = $NameTranslateType.InvokeMember( 'Get', 'InvokeMethod', $null, $NameTranslateComObject, [TranslateType]::NTAccount ) $NTAccount.Translate( [System.Security.Principal.SecurityIdentifier] ) # get the requested format } else { $NameTranslateType.InvokeMember( 'Get', 'InvokeMethod', $null, $NameTranslateComObject, $OutputType ) } } } <# .SYNOPSIS Helper function to simplify creating dynamic parameters .DESCRIPTION Helper function to simplify creating dynamic parameters Example use cases: Include parameters only if your environment dictates it Include parameters depending on the value of a user-specified parameter Provide tab completion and intellisense for parameters, depending on the environment Please keep in mind that all dynamic parameters you create will not have corresponding variables created. One of the examples illustrates a generic method for populating appropriate variables from dynamic parameters Alternatively, manually reference $PSBoundParameters for the dynamic parameter value .NOTES Credit to http://jrich523.wordpress.com/2013/05/30/powershell-simple-way-to-add-dynamic-parameters-to-advanced-function/ Added logic to make option set optional Added logic to add RuntimeDefinedParameter to existing DPDictionary Added a little comment based help Credit to BM for alias and type parameters and their handling .PARAMETER Name Name of the dynamic parameter .PARAMETER Type Type for the dynamic parameter. Default is string .PARAMETER Alias If specified, one or more aliases to assign to the dynamic parameter .PARAMETER ValidateSet If specified, set the ValidateSet attribute of this dynamic parameter .PARAMETER Mandatory If specified, set the Mandatory attribute for this dynamic parameter .PARAMETER ParameterSetName If specified, set the ParameterSet attribute for this dynamic parameter .PARAMETER Position If specified, set the Position attribute for this dynamic parameter .PARAMETER ValueFromPipelineByPropertyName If specified, set the ValueFromPipelineByPropertyName attribute for this dynamic parameter .PARAMETER HelpMessage If specified, set the HelpMessage for this dynamic parameter .PARAMETER DPDictionary If specified, add resulting RuntimeDefinedParameter to an existing RuntimeDefinedParameterDictionary (appropriate for multiple dynamic parameters) If not specified, create and return a RuntimeDefinedParameterDictionary (appropriate for a single dynamic parameter) See final example for illustration .EXAMPLE function Show-Free { [CmdletBinding()] Param() DynamicParam { $options = @( gwmi win32_volume | %{$_.driveletter} | sort ) New-DynamicParam -Name Drive -ValidateSet $options -Position 0 -Mandatory } begin{ #have to manually populate $drive = $PSBoundParameters.drive } process{ $vol = gwmi win32_volume -Filter "driveletter='$drive'" "{0:N2}% free on {1}" -f ($vol.Capacity / $vol.FreeSpace),$drive } } #Show-Free Show-Free -Drive <tab> # This example illustrates the use of New-DynamicParam to create a single dynamic parameter # The Drive parameter ValidateSet populates with all available volumes on the computer for handy tab completion / intellisense .EXAMPLE # I found many cases where I needed to add more than one dynamic parameter # The DPDictionary parameter lets you specify an existing dictionary # The block of code in the Begin block loops through bound parameters and defines variables if they don't exist Function Test-DynPar{ [cmdletbinding()] param( [string[]]$x = $Null ) DynamicParam { #Create the RuntimeDefinedParameterDictionary $Dictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary New-DynamicParam -Name AlwaysParam -ValidateSet @( gwmi win32_volume | %{$_.driveletter} | sort ) -DPDictionary $Dictionary #Add dynamic parameters to $dictionary if($x -eq 1) { New-DynamicParam -Name X1Param1 -ValidateSet 1,2 -mandatory -DPDictionary $Dictionary New-DynamicParam -Name X1Param2 -DPDictionary $Dictionary New-DynamicParam -Name X3Param3 -DPDictionary $Dictionary -Type DateTime } else { New-DynamicParam -Name OtherParam1 -Mandatory -DPDictionary $Dictionary New-DynamicParam -Name OtherParam2 -DPDictionary $Dictionary New-DynamicParam -Name OtherParam3 -DPDictionary $Dictionary -Type DateTime } #return RuntimeDefinedParameterDictionary $Dictionary } Begin { #This standard block of code loops through bound parameters... #If no corresponding variable exists, one is created #Get common parameters, pick out bound parameters not in that set Function _temp { [cmdletbinding()] param() } $BoundKeys = $PSBoundParameters.keys | Where-Object { (get-command _temp | select -ExpandProperty parameters).Keys -notcontains $_} foreach($param in $BoundKeys) { if (-not ( Get-Variable -name $param -scope 0 -ErrorAction SilentlyContinue ) ) { New-Variable -Name $Param -Value $PSBoundParameters.$param Write-Verbose "Adding variable for dynamic parameter '$param' with value '$($PSBoundParameters.$param)'" } } #Appropriate variables should now be defined and accessible Get-Variable -scope 0 } } # This example illustrates the creation of many dynamic parameters using New-DynamicParam # You must create a RuntimeDefinedParameterDictionary object ($dictionary here) # To each New-DynamicParam call, add the -DPDictionary parameter pointing to this RuntimeDefinedParameterDictionary # At the end of the DynamicParam block, return the RuntimeDefinedParameterDictionary # Initialize all bound parameters using the provided block or similar code .FUNCTIONALITY PowerShell Language #> Function New-DynamicParam { param( [string] $Name, [System.Type] $Type = [string], [string[]] $Alias = @(), [string[]] $ValidateSet, [switch] $Mandatory, [string] $ParameterSetName="__AllParameterSets", [int] $Position, [switch] $ValueFromPipelineByPropertyName, [string] $HelpMessage, [validatescript({ if(-not ( $_ -is [System.Management.Automation.RuntimeDefinedParameterDictionary] -or -not $_) ) { Throw "DPDictionary must be a System.Management.Automation.RuntimeDefinedParameterDictionary object, or not exist" } $true })] $DPDictionary = $false ) #Create attribute object, add attributes, add to collection $ParamAttr = New-Object System.Management.Automation.ParameterAttribute $ParamAttr.ParameterSetName = $ParameterSetName if($mandatory) { $ParamAttr.Mandatory = $true } if($Position -ne $null) { $ParamAttr.Position=$Position } if($ValueFromPipelineByPropertyName) { $ParamAttr.ValueFromPipelineByPropertyName = $true } if($HelpMessage) { $ParamAttr.HelpMessage = $HelpMessage } $AttributeCollection = New-Object 'Collections.ObjectModel.Collection[System.Attribute]' $AttributeCollection.Add($ParamAttr) #param validation set if specified if($ValidateSet) { $ParamOptions = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $ValidateSet $AttributeCollection.Add($ParamOptions) } #Aliases if specified if($Alias.count -gt 0) { $ParamAlias = New-Object System.Management.Automation.AliasAttribute -ArgumentList $Alias $AttributeCollection.Add($ParamAlias) } #Create the dynamic parameter $Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection) #Add the dynamic parameter to an existing dynamic parameter dictionary, or create the dictionary and add it if($DPDictionary) { $DPDictionary.Add($Name, $Parameter) } else { $Dictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $Dictionary.Add($Name, $Parameter) $Dictionary } } # cleanup $ExecutionContext.SessionState.Module.OnRemove = { # cleanup when unloading module (if any) } # SIG # Begin signature block # MIIesgYJKoZIhvcNAQcCoIIeozCCHp8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUbBJ/eXldiu/a0mlG1WEGkJuA # nfugghm9MIIEhDCCA2ygAwIBAgIQQhrylAmEGR9SCkvGJCanSzANBgkqhkiG9w0B # AQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNV # BAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRU # cnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEw # NDgzOFowgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2Fs # dCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8G # A1UECxMYaHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNF # UkZpcnN0LU9iamVjdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6q # gT+jo2F4qjEAVZURnicPHxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x # 2AogZ8f02b+U60cEPgLOKqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQ # w5ujm9M89RKZd7G3CeBo5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbH # d2pBnqcP1/vulBe3/IW+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh # 2JU022R5KP+6LhHC5ehbkkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzT # bafc8H9vg2XiaquHhnUCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rE # JlTvA73gJMtUGjAdBgNVHQ4EFgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwDgYDVR0P # AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQG # A1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVz # dEV4dGVybmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGG # GWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBAE1C # L6bBiusHgJBYRoz4GTlmKjxaLG3P1NmHVY15CxKIe0CP1cf4S41VFmOtt1fcOyu9 # 08FPHgOHS0Sb4+JARSbzJkkraoTxVHrUQtr802q7Zn7Knurpu9wHx8OSToM8gUmf # ktUyCepJLqERcZo20sVOaLbLDhslFq9s3l122B9ysZMmhhfbGN6vRenf+5ivFBjt # pF72iZRF8FUESt3/J90GSkD2tLzx5A+ZArv9XQ4uKMG+O18aP5cQhLwWPtijnGMd # ZstcX9o+8w8KCTUi29vAPwD55g1dZ9H9oB4DK9lA977Mh2ZUgKajuPUZYtXSJrGY # Ju6ay0SnRVqBlRUa9VEwggTmMIIDzqADAgECAhBiXE2QjNVC+6supXM/8VQZMA0G # CSqGSIb3DQEBBQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNV # BAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdv # cmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMU # VVROLVVTRVJGaXJzdC1PYmplY3QwHhcNMTEwNDI3MDAwMDAwWhcNMjAwNTMwMTA0 # ODM4WjB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVy # MRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEg # MB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB # AQUAA4IBDwAwggEKAoIBAQCqgvGEqVvYcbXSXSvt9BMgDPmb6dGPdF5u7uspSNjI # vizrCmFgzL2SjXzddLsKnmhOqnUkcyeuN/MagqVtuMgJRkx+oYPp4gNgpCEQJ0Ca # WeFtrz6CryFpWW1jzM6x9haaeYOXOh0Mr8l90U7Yw0ahpZiqYM5V1BIR8zsLbMaI # upUu76BGRTl8rOnjrehXl1/++8IJjf6OmqU/WUb8xy1dhIfwb1gmw/BC/FXeZb5n # OGOzEbGhJe2pm75I30x3wKoZC7b9So8seVWx/llaWm1VixxD9rFVcimJTUA/vn9J # AV08m1wI+8ridRUFk50IYv+6Dduq+LW/EDLKcuoIJs0ZAgMBAAGjggFKMIIBRjAf # BgNVHSMEGDAWgBTa7WR0FJwUPKvdmam9WyhNizzJ2DAdBgNVHQ4EFgQUZCKGtkqJ # yQQP0ARYkiuzbj0eJ2wwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C # AQAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEQYDVR0gBAowCDAGBgRVHSAAMEIGA1Ud # HwQ7MDkwN6A1oDOGMWh0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZp # cnN0LU9iamVjdC5jcmwwdAYIKwYBBQUHAQEEaDBmMD0GCCsGAQUFBzAChjFodHRw # Oi8vY3J0LnVzZXJ0cnVzdC5jb20vVVROQWRkVHJ1c3RPYmplY3RfQ0EuY3J0MCUG # CCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEB # BQUAA4IBAQARyT3hBeg7ZazJdDEDt9qDOMaSuv3N+Ntjm30ekKSYyNlYaDS18Ash # U55ZRv1jhd/+R6pw5D9eCJUoXxTx/SKucOS38bC2Vp+xZ7hog16oYNuYOfbcSV4T # p5BnS+Nu5+vwQ8fQL33/llqnA9abVKAj06XCoI75T9GyBiH+IV0njKCv2bBS7vzI # 7bec8ckmONalMu1Il5RePeA9NbSwyVivx1j/YnQWkmRB2sqo64sDvcFOrh+RMrjh # JDt77RRoCYaWKMk7yWwowiVp9UphreAn+FOndRWwUTGw8UH/PlomHmB+4uNqOZrE # 6u4/5rITP1UDBE0LkHLU6/u8h5BRsjgZMIIE/jCCA+agAwIBAgIQK3PbdGMRTFpb # MkryMFdySTANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMS # R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD # T01PRE8gQ0EgTGltaXRlZDEgMB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcg # Q0EwHhcNMTkwNTAyMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjCBgzELMAkGA1UEBhMC # R0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9y # ZDEYMBYGA1UECgwPU2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDDCJTZWN0aWdvIFNI # QS0xIFRpbWUgU3RhbXBpbmcgU2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A # MIIBCgKCAQEAv1I2gjrcdDcNeNV/FlAZZu26GpnRYziaDGayQNungFC/aS42Lwpn # P0ChSopjNZvQGcx0qhcZkSu1VSAZ+8AaOm3KOZuC8rqVoRrYNMe4iXtwiHBRZmns # d/7GlHJ6zyWB7TSCmt8IFTcxtG2uHL8Y1Q3P/rXhxPuxR3Hp+u5jkezx7M5ZBBF8 # rgtgU+oq874vAg/QTF0xEy8eaQ+Fm0WWwo0Si2euH69pqwaWgQDfkXyVHOaeGWTf # dshgRC9J449/YGpFORNEIaW6+5H6QUDtTQK0S3/f4uA9uKrzGthBg49/M+1BBuJ9 # nj9ThI0o2t12xr33jh44zcDLYCQD3npMqwIDAQABo4IBdDCCAXAwHwYDVR0jBBgw # FoAUZCKGtkqJyQQP0ARYkiuzbj0eJ2wwHQYDVR0OBBYEFK7u2WC6XvUsARL9jo2y # VXI1Rm/xMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM # MAoGCCsGAQUFBwMIMEAGA1UdIAQ5MDcwNQYMKwYBBAGyMQECAQMIMCUwIwYIKwYB # BQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMEIGA1UdHwQ7MDkwN6A1oDOG # MWh0dHA6Ly9jcmwuc2VjdGlnby5jb20vQ09NT0RPVGltZVN0YW1waW5nQ0FfMi5j # cmwwcgYIKwYBBQUHAQEEZjBkMD0GCCsGAQUFBzAChjFodHRwOi8vY3J0LnNlY3Rp # Z28uY29tL0NPTU9ET1RpbWVTdGFtcGluZ0NBXzIuY3J0MCMGCCsGAQUFBzABhhdo # dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAen+pStKw # pBwdDZ0tXMauWt2PRR3wnlyQ9l6scP7T2c3kGaQKQ3VgaoOkw5mEIDG61v5MzxP4 # EPdUCX7q3NIuedcHTFS3tcmdsvDyHiQU0JzHyGeqC2K3tPEG5OfkIUsZMpk0uRlh # dwozkGdswIhKkvWhQwHzrqJvyZW9ljj3g/etfCgf8zjfjiHIcWhTLcuuquIwF4Mi # KRi14YyJ6274fji7kE+5Xwc0EmuX1eY7kb4AFyFu4m38UnnvgSW6zxPQ+90rzYG2 # V4lO8N3zC0o0yoX/CLmWX+sRE+DhxQOtVxzhXZIGvhvIPD+lIJ9p0GnBxcLJPufF # cvfqG5bilK+GLjCCBUwwggQ0oAMCAQICEQCV7K1bRdp1yZPPBYrFbG8VMA0GCSqG # SIb3DQEBCwUAMHwxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNo # ZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRl # ZDEkMCIGA1UEAxMbU2VjdGlnbyBSU0EgQ29kZSBTaWduaW5nIENBMB4XDTE5MTAx # NTAwMDAwMFoXDTIwMTAwNzIzNTk1OVowgZQxCzAJBgNVBAYTAlVTMQ4wDAYDVQQR # DAU2MDEyMDERMA8GA1UECAwISWxsaW5vaXMxDjAMBgNVBAcMBUVsZ2luMRowGAYD # VQQJDBExMjg3IEJsYWNraGF3ayBEcjEaMBgGA1UECgwRU2hhbm5vbiBHcmF5YnJv # b2sxGjAYBgNVBAMMEVNoYW5ub24gR3JheWJyb29rMIIBIjANBgkqhkiG9w0BAQEF # AAOCAQ8AMIIBCgKCAQEA1A3wiJRalXGleCYOLaKdlD5iZrswpu4ChSnCx8XvkWeL # R/XBQSvebJXpF99sdVwwUeouEk1i5EA2AIU88DoEw0+1XxC6DAUwYAVXmo3M+dkv # OwNXHrWwSRqNwmhABHVejGOInKsi1jYa3DPI2dFBL19Trg0ez0oXkMVwbKGDpwt9 # U7WbbjveLcAPnpvR65dk3Jhb9bmCMirCnALjaOOnFzlCUiagx9nDszzw7fYRAlf6 # EJNnicwwBujOmA59q9urwAuEA7/VXTAMpE2wmhVsM4xqscbzAPs7PSVgkOTrZR6a # 51r1HSCzrULISVZKxF0mD4/6qOElqM/X/nd7q7dmSQIDAQABo4IBrjCCAaowHwYD # VR0jBBgwFoAUDuE6qFM6MdWKvsG7rWcaA4WtNA4wHQYDVR0OBBYEFJHiTLW7XSJv # Xn/hpQzh7bxSIUZ2MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBMGA1Ud # JQQMMAoGCCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBABgNVHSAEOTA3MDUG # DCsGAQQBsjEBAgEDAjAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3RpZ28uY29t # L0NQUzBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLnNlY3RpZ28uY29tL1Nl # Y3RpZ29SU0FDb2RlU2lnbmluZ0NBLmNybDBzBggrBgEFBQcBAQRnMGUwPgYIKwYB # BQUHMAKGMmh0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1JTQUNvZGVTaWdu # aW5nQ0EuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAm # BgNVHREEHzAdgRtzaGFubm9uLmdyYXlicm9va0BnbWFpbC5jb20wDQYJKoZIhvcN # AQELBQADggEBACNm23H5GuT8THomfaxBDdgN/4g4FgsClLsxhAyRyWxqnE4udxre # x1Dq3FQtdXoeXFPaaFYVH/zvmEFuh+oz65Ejomo2WPSOVKiF6NbLpxScHW2c1+yO # NHDqn/TGtx0+RrfUgOFgao/AzuRqxei90CotgUe73cpmG0JPdmV1+hnMAhojoO4g # bhfdb69y8fCaDzLoTmybz1JOfcinR12TLntNV+Def2CXaNoOV2VNKpauAiIh2BkK # 7LoabyBtMNQbMNCY33dyNq9V7tvVxdYOlPRoANB3SfATPtKQCrix7T85qrFoRHBC # SxTfYFHsyGQVno6lmMfQstJ6q+TQJz1gFcUwggX1MIID3aADAgECAhAdokgwb5sm # GNCC4JZ9M9NqMA0GCSqGSIb3DQEBDAUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRo # ZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0 # aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xODExMDIwMDAwMDBaFw0zMDEyMzEyMzU5 # NTlaMHwxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIx # EDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEkMCIG # A1UEAxMbU2VjdGlnbyBSU0EgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0B # AQEFAAOCAQ8AMIIBCgKCAQEAhiKNMoV6GJ9J8JYvYwgeLdx8nxTP4ya2JWYpQIZU # RnQxYsUQ7bKHJ6aZy5UwwFb1pHXGqQ5QYqVRkRBq4Etirv3w+Bisp//uLjMg+gwZ # iahse60Aw2Gh3GllbR9uJ5bXl1GGpvQn5Xxqi5UeW2DVftcWkpwAL2j3l+1qcr44 # O2Pej79uTEFdEiAIWeg5zY/S1s8GtFcFtk6hPldrH5i8xGLWGwuNx2YbSp+dgcRy # QLXiX+8LRf+jzhemLVWwt7C8VGqdvI1WU8bwunlQSSz3A7n+L2U18iLqLAevRtn5 # RhzcjHxxKPP+p8YU3VWRbooRDd8GJJV9D6ehfDrahjVh0wIDAQABo4IBZDCCAWAw # HwYDVR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFA7hOqhT # OjHVir7Bu61nGgOFrTQOMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/ # AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMDBggrBgEFBQcDCDARBgNVHSAECjAIMAYG # BFUdIAAwUAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29t # L1VTRVJUcnVzdFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHYGCCsGAQUF # BwEBBGowaDA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29tL1VT # RVJUcnVzdFJTQUFkZFRydXN0Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2Nz # cC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBDAUAA4ICAQBNY1DtRzRKYaTb3moq # jJvxAAAeHWJ7Otcywvaz4GOz+2EAiJobbRAHBE++uOqJeCLrD0bs80ZeQEaJEvQL # d1qcKkE6/Nb06+f3FZUzw6GDKLfeL+SU94Uzgy1KQEi/msJPSrGPJPSzgTfTt2Sw # piNqWWhSQl//BOvhdGV5CPWpk95rcUCZlrp48bnI4sMIFrGrY1rIFYBtdF5KdX6l # uMNstc/fSnmHXMdATWM19jDTz7UKDgsEf6BLrrujpdCEAJM+U100pQA1aWy+nyAl # EA0Z+1CQYb45j3qOTfafDh7+B1ESZoMmGUiVzkrJwX/zOgWb+W/fiH/AI57SHkN6 # RTHBnE2p8FmyWRnoao0pBAJ3fEtLzXC+OrJVWng+vLtvAxAldxU0ivk2zEOS5LpP # 8WKTKCVXKftRGcehJUBqhFfGsp2xvBwK2nxnfn0u6ShMGH7EezFBcZpLKewLPVdQ # 0srd/Z4FUeVEeN0B3rF1mA1UJP3wTuPi+IO9crrLPTru8F4XkmhtyGH5pvEqCgul # ufSe7pgyBYWe6/mDKdPGLH29OncuizdCoGqC7TtKqpQQpOEN+BfFtlp5MxiS47V1 # +KHpjgolHuQe8Z9ahyP/n6RRnvs5gBHN27XEp6iAb+VT1ODjosLSWxr6MiYtaldw # HDykWC6j81tLB9wyWfOHpxptWDGCBF8wggRbAgEBMIGRMHwxCzAJBgNVBAYTAkdC # MRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQx # GDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEkMCIGA1UEAxMbU2VjdGlnbyBSU0Eg # Q29kZSBTaWduaW5nIENBAhEAleytW0XadcmTzwWKxWxvFTAJBgUrDgMCGgUAoHgw # GAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGC # NwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQx # FgQUCYrI7+Luoc7FPYuktn/e9KoLFFowDQYJKoZIhvcNAQEBBQAEggEAG9qH9aIv # ZeOo8nImP3LDg0WhcNNuSmmmKkMZIX1PixxoXtgTEqzO0orOS7vlJpbLcsKAsnaA # NmmfCrS9GtICmEWizzfMlmH7EtWLNpP45zMtzlNXCK09IKE4cUbePdB41ZSQjnvD # dscY4yFh874lsLYIR6Ck6FbZJnTsGUy6sE3MDsHn+Iaqxeck0fNtBHTkHz3Dfrmf # PcsGEdWzcmugTJgHP9IKJhomz5Ny7/Mk00a0MN6f6MrhhCdbSfkUvWlk3c44ei6g # MRqyhIosuUnA7esftgKkv6CsZ47UcArZ6r5VdENp+wH82SuwrPvQbdA2VF6QrXfw # EKgo7pKO+zpl6KGCAigwggIkBgkqhkiG9w0BCQYxggIVMIICEQIBATCBjjB6MQsw # CQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQH # EwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEgMB4GA1UEAxMX # Q09NT0RPIFRpbWUgU3RhbXBpbmcgQ0ECECtz23RjEUxaWzJK8jBXckkwCQYFKw4D # AhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X # DTIwMDQzMDE2NDgxMVowIwYJKoZIhvcNAQkEMRYEFP31wJPjLaqoNdSNL+FiQ5ss # 0Y/IMA0GCSqGSIb3DQEBAQUABIIBADceQqX33GN9rv9b4TiBjjxLAD7hgv8IBaKE # 2e77Re7U9WM3OBjQABVZ+b/G1VAxF6nGOJsnR/U9fUz7DJepcf2fgicWX123uD2P # WwWH4d4UEJ2rBB/NNOpkAB6vqOXHszj7tpWJgq2tt4X+O+V1dd3hEPjFwyQGuOx8 # DF0DWDEgsz+kT7ob0Qg+CNBRrSB/e7ROkRtMkamo5EdmbII4QFTxEK55eAKk18Jr # bfF2GHoYStkmd4LwaJP8mah+fE5uuHiaMvg/U1bS7bGcxxuPmiM0hoRszsuAz1B9 # xliZeuXMria6eF2bYAfX+pty572K2oy+pRuDvxxKb+bNxTAXIBQ= # SIG # End signature block |