GitDsc.psm1
|
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. using namespace System.Collections.Generic enum Ensure { Absent Present } enum ConfigLocation { none global system worktree local } #region DSCResources <# .SYNOPSIS The `GitClone` DSC resource is used to clone a Git repository to a local directory. .DESCRIPTION The `GitClone` DSC resource clones a remote Git repository specified by its HTTPS URL into a local root directory. If the repository has already been cloned, the resource will confirm the existing remote matches the specified URL. ## Requirements * Target machine must have Git installed. .PARAMETER HttpsUrl The HTTPS URL of the Git repository to clone. This is a key property. .PARAMETER Ensure Specifies whether the repository should be present or absent. Defaults to `Present`. Removing a cloned repository is not supported by this resource. .PARAMETER RemoteName The name of the remote. Defaults to `origin`. .PARAMETER RootDirectory The root directory where the repository will be cloned into. This is a mandatory property. .PARAMETER FolderName The folder name for the cloned repository. If not specified, it is derived from the HTTPS URL. .PARAMETER ExtraArgs Additional arguments to pass to `git clone`, provided as an array of strings where each element is a separate argument. .EXAMPLE Invoke-DscResource -ModuleName GitDsc -Name GitClone -Method Set -Property @{ HttpsUrl = 'https://github.com/microsoft/winget-dsc' RootDirectory = 'C:\repos' } This example clones the winget-dsc repository into C:\repos. .EXAMPLE Invoke-DscResource -ModuleName GitDsc -Name GitClone -Method Set -Property @{ HttpsUrl = 'https://github.com/microsoft/winget-dsc' RootDirectory = 'C:\repos' ExtraArgs = @('--depth', '1') } This example performs a shallow clone of the winget-dsc repository into C:\repos. #> [DSCResource()] class GitClone { [DscProperty()] [Ensure]$Ensure = [Ensure]::Present [DscProperty(Key)] [string]$HttpsUrl [DscProperty()] [string]$RemoteName # The root directory where the project will be cloned to. (i.e. the directory where you expect to run `git clone`) [DscProperty(Mandatory)] [string]$RootDirectory # The folder name where the repository will be cloned to. If not specified, it will be derived from the HTTPS URL. [DscProperty()] [string]$FolderName [DscProperty()] [string[]]$ExtraArgs [GitClone] Get() { Assert-Git $currentState = [GitClone]::new() $currentState.HttpsUrl = $this.HttpsUrl $currentState.RootDirectory = $this.RootDirectory $currentState.FolderName = $this.FolderName $currentState.Ensure = [Ensure]::Absent $currentState.RemoteName = ($null -eq $this.RemoteName) ? 'origin' : $this.RemoteName if (-not(Test-Path -Path $this.RootDirectory)) { return $currentState } # Check if the URL is a Git repository URL Assert-GitUrl -HttpsUrl $this.HttpsUrl Set-Location $this.RootDirectory $projectName = $this.FolderName ? $this.FolderName : (GetGitProjectName($this.HttpsUrl)) $expectedDirectory = Join-Path -Path $this.RootDirectory -ChildPath $projectName if (Test-Path $expectedDirectory) { Set-Location -Path $expectedDirectory try { $gitRemoteValue = Invoke-GitRemote -Arguments @('get-url', $currentState.RemoteName) if ($this.HttpsUrl.StartsWith($gitRemoteValue)) { $currentState.Ensure = [Ensure]::Present } } catch { # Failed to execute `git remote`. Ensure state is `absent` } } return $currentState } [bool] Test() { $currentState = $this.Get() return $currentState.Ensure -eq $this.Ensure } [void] Set() { if ($this.Ensure -eq [Ensure]::Absent) { throw 'This resource does not support removing a cloned repository.' } if (-not(Test-Path $this.RootDirectory)) { New-Item -ItemType Directory -Path $this.RootDirectory } Set-Location $this.RootDirectory $cloneArgs = [List[string]]::new() if ($this.ExtraArgs) { foreach ($a in ($this.ExtraArgs | Where-Object { $_ })) { $cloneArgs.Add($a) } } $cloneArgs.Add($this.HttpsUrl) if ($this.FolderName) { $cloneArgs.Add($this.FolderName) } Invoke-GitClone -Arguments $cloneArgs } } <# .SYNOPSIS The `GitRemote` DSC resource is used to manage remote repository references in a Git project. .DESCRIPTION The `GitRemote` DSC resource adds or removes a named remote URL from an existing local Git repository. The project directory must already exist as a valid Git repository. ## Requirements * Target machine must have Git installed. * The project directory must be an existing Git repository. .PARAMETER RemoteName The name of the Git remote. This is a key property. .PARAMETER RemoteUrl The URL of the Git remote. This is a key property. .PARAMETER Ensure Specifies whether the remote should be present or absent. Defaults to `Present`. .PARAMETER ProjectDirectory The path to the local Git repository. This is a mandatory property. .EXAMPLE Invoke-DscResource -ModuleName GitDsc -Name GitRemote -Method Set -Property @{ RemoteName = 'upstream' RemoteUrl = 'https://github.com/microsoft/winget-dsc' ProjectDirectory = 'C:\repos\winget-dsc' } This example adds an upstream remote to the specified local Git repository. #> [DSCResource()] class GitRemote { [DscProperty()] [Ensure]$Ensure = [Ensure]::Present [DscProperty(Key)] [string]$RemoteName [DscProperty(Key)] [string]$RemoteUrl # The root directory where the project will be cloned to. (i.e. the directory where you expect to run `git clone`) [DscProperty(Mandatory)] [string]$ProjectDirectory [GitRemote] Get() { $currentState = [GitRemote]::new() $currentState.RemoteName = $this.RemoteName $currentState.RemoteUrl = $this.RemoteUrl $currentState.ProjectDirectory = $this.ProjectDirectory if (-not(Test-Path -Path $this.ProjectDirectory)) { throw 'Project directory does not exist.' } Set-Location $this.ProjectDirectory try { $gitRemoteValue = Invoke-GitRemote -Arguments @('get-url', $this.RemoteName) $currentState.Ensure = ($gitRemoteValue -like $this.RemoteUrl) ? [Ensure]::Present : [Ensure]::Absent } catch { $currentState.Ensure = [Ensure]::Absent } return $currentState } [bool] Test() { $currentState = $this.Get() return $currentState.Ensure -eq $this.Ensure } [void] Set() { Set-Location $this.ProjectDirectory if ($this.Ensure -eq [Ensure]::Present) { try { Invoke-GitRemote -Arguments @('add', $this.RemoteName, $this.RemoteUrl) } catch { throw 'Failed to add remote repository.' } } else { try { Invoke-GitRemote -Arguments @('remove', $this.RemoteName) } catch { throw 'Failed to remove remote repository.' } } } } <# .SYNOPSIS The `GitConfigUserName` DSC resource is used to manage the Git user name configuration. .DESCRIPTION The `GitConfigUserName` DSC resource sets or removes the `user.name` Git configuration value at the specified configuration scope (local, global, system, or worktree). ## Requirements * Target machine must have Git installed. * For system-level configuration, the resource must be run as an Administrator. .PARAMETER UserName The Git user name to configure. This is a key property. .PARAMETER Ensure Specifies whether the user name should be present or absent. Defaults to `Present`. .PARAMETER ConfigLocation The Git configuration scope to apply the setting to (e.g., `global`, `system`, `local`). .PARAMETER ProjectDirectory The path to the Git repository. Required for non-global and non-system configurations. .EXAMPLE Invoke-DscResource -ModuleName GitDsc -Name GitConfigUserName -Method Set -Property @{ UserName = 'Demitrius Nelon' ConfigLocation = 'global' } This example sets the global Git user name to 'Demitrius Nelon'. #> [DSCResource()] class GitConfigUserName { [DscProperty()] [Ensure]$Ensure = [Ensure]::Present [DscProperty(Key)] [string]$UserName [DscProperty()] [ConfigLocation]$ConfigLocation [DscProperty()] [string]$ProjectDirectory [GitConfigUserName] Get() { $currentState = [GitConfigUserName]::new() $currentState.UserName = $this.UserName $currentState.ConfigLocation = $this.ConfigLocation $currentState.ProjectDirectory = $this.ProjectDirectory if ($this.ConfigLocation -ne [ConfigLocation]::global -and $this.ConfigLocation -ne [ConfigLocation]::system) { # Project directory is not required for --global or --system configurations if ($this.ProjectDirectory) { if (Test-Path -Path $this.ProjectDirectory) { Set-Location $this.ProjectDirectory } else { throw 'Project directory does not exist.' } } else { throw 'Project directory parameter must be specified for non-system and non-global configurations.' } } $configArgs = ConstructGitConfigUserArguments -Arguments @('user.name') -ConfigLocation $this.ConfigLocation $result = Invoke-GitConfig -Arguments $configArgs $currentState.Ensure = ($currentState.UserName -eq $result) ? [Ensure]::Present : [Ensure]::Absent return $currentState } [bool] Test() { $currentState = $this.Get() return $currentState.Ensure -eq $this.Ensure } [void] Set() { if ($this.ConfigLocation -eq [ConfigLocation]::system) { Assert-IsAdministrator } if ($this.ConfigLocation -ne [ConfigLocation]::global -and $this.ConfigLocation -ne [ConfigLocation]::system) { Set-Location $this.ProjectDirectory } if ($this.Ensure -eq [Ensure]::Present) { $configArgs = ConstructGitConfigUserArguments -Arguments @('user.name', $this.UserName) -ConfigLocation $this.ConfigLocation } else { $configArgs = ConstructGitConfigUserArguments -Arguments @('--unset', 'user.name') -ConfigLocation $this.ConfigLocation } Invoke-GitConfig -Arguments $configArgs } } <# .SYNOPSIS The `GitConfigUserEmail` DSC resource is used to manage the Git user email configuration. .DESCRIPTION The `GitConfigUserEmail` DSC resource sets or removes the `user.email` Git configuration value at the specified configuration scope (local, global, system, or worktree). ## Requirements * Target machine must have Git installed. * For system-level configuration, the resource must be run as an Administrator. .PARAMETER UserEmail The Git user email to configure. This is a key property. .PARAMETER Ensure Specifies whether the user email should be present or absent. Defaults to `Present`. .PARAMETER ConfigLocation The Git configuration scope to apply the setting to (e.g., `global`, `system`, `local`). .PARAMETER ProjectDirectory The path to the Git repository. Required for non-global and non-system configurations. .EXAMPLE Invoke-DscResource -ModuleName GitDsc -Name GitConfigUserEmail -Method Set -Property @{ UserEmail = 'demitrius.nelon@example.com' ConfigLocation = 'global' } This example sets the global Git user email to 'demitrius.nelon@example.com'. #> [DSCResource()] class GitConfigUserEmail { [DscProperty()] [Ensure]$Ensure = [Ensure]::Present [DscProperty(Key)] [string]$UserEmail [DscProperty()] [ConfigLocation]$ConfigLocation [DscProperty()] [string]$ProjectDirectory [GitConfigUserEmail] Get() { $currentState = [GitConfigUserEmail]::new() $currentState.UserEmail = $this.UserEmail $currentState.ConfigLocation = $this.ConfigLocation $currentState.ProjectDirectory = $this.ProjectDirectory if ($this.ConfigLocation -ne [ConfigLocation]::global -and $this.ConfigLocation -ne [ConfigLocation]::system) { # Project directory is not required for --global or --system configurations if ($this.ProjectDirectory) { if (Test-Path -Path $this.ProjectDirectory) { Set-Location $this.ProjectDirectory } else { throw 'Project directory does not exist.' } } else { throw 'Project directory parameter must be specified for non-system and non-global configurations.' } } $configArgs = ConstructGitConfigUserArguments -Arguments @('user.email') -ConfigLocation $this.ConfigLocation $result = Invoke-GitConfig -Arguments $configArgs $currentState.Ensure = ($currentState.UserEmail -eq $result) ? [Ensure]::Present : [Ensure]::Absent return $currentState } [bool] Test() { $currentState = $this.Get() return $currentState.Ensure -eq $this.Ensure } [void] Set() { if ($this.ConfigLocation -eq [ConfigLocation]::system) { Assert-IsAdministrator } if ($this.ConfigLocation -ne [ConfigLocation]::global -and $this.ConfigLocation -ne [ConfigLocation]::system) { Set-Location $this.ProjectDirectory } if ($this.Ensure -eq [Ensure]::Present) { $configArgs = ConstructGitConfigUserArguments -Arguments @('user.email', $this.UserEmail) -ConfigLocation $this.ConfigLocation } else { $configArgs = ConstructGitConfigUserArguments -Arguments @('--unset', 'user.email') -ConfigLocation $this.ConfigLocation } Invoke-GitConfig -Arguments $configArgs } } #endregion DSCResources #region Functions function Assert-Git { # Refresh session $path value before invoking 'git' $env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User') try { Invoke-Git -Command 'help' return } catch { throw 'Git is not installed' } } function GetGitProjectName { param( [Parameter()] [string]$HttpsUrl ) $projectName = ($HttpsUrl.split('/')[-1]).split('.')[0] return $projectName } function Invoke-GitConfig { param( [Parameter()] [string[]]$Arguments = @() ) return Invoke-Git -Command (@('config') + $Arguments) } function Invoke-GitRemote { param( [Parameter()] [string[]]$Arguments = @() ) return Invoke-Git -Command (@('remote') + $Arguments) } function Invoke-GitClone { param( [Parameter()] [string[]]$Arguments = @() ) return Invoke-Git -Command (@('clone') + $Arguments) } function Invoke-Git { param ( [Parameter(Mandatory = $true)] [string[]]$Command ) $argList = @($Command | Where-Object { -not [string]::IsNullOrEmpty($_) }) return & git @argList } function ConstructGitConfigUserArguments { param( [Parameter(Mandatory)] [string[]]$Arguments, [Parameter(Mandatory)] [ConfigLocation]$ConfigLocation ) if ([ConfigLocation]::None -ne $ConfigLocation) { return @("--$ConfigLocation") + $Arguments } return $Arguments } function Assert-IsAdministrator { $windowsIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent() $windowsPrincipal = New-Object -TypeName 'System.Security.Principal.WindowsPrincipal' -ArgumentList @( $windowsIdentity ) $adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator if (-not $windowsPrincipal.IsInRole($adminRole)) { throw 'This resource must be run as an Administrator to modify system settings.' } } function Assert-GitUrl { param( [Parameter(Mandatory)] [string]$HttpsUrl ) $out = Invoke-Git -Command @('ls-remote', $HttpsUrl, '*') 2>&1 if ($LASTEXITCODE -ne 0) { throw "Invalid Git URL: $HttpsUrl. Error: $out" } } #endregion Functions # SIG # Begin signature block # MIInbgYJKoZIhvcNAQcCoIInXzCCJ1sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBBSkHauk5/cCV2 # LPOHBxcDJOsMrRRwCe6kcCJA/z7mCaCCDMkwggYEMIID7KADAgECAhMzAAACHPrN # xZvoL37EAAAAAAIcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQxWhcNMjcwNDE1MTg1 # OTQxWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDVsZfgOKmM31HPfoWOoNEiw0SlCiIxUMC0I9NMWbucKOw/e9lP # oAoehQVu6SG65V4EPzrYsnBnFPNoi4/HoOdjhz1qkrEt4I6tEcxXU6oOeY9zGveC # /3iBeuhLYxM3M/PkcUoebF+Nednm8OkdSPoDu8imViHPQq/8CQUu0WRR4rE+dMRf # rpVqfmNi2qWCX94T4MsepijGVkwE//tJg0ryAiYdHT34LSnlG/RSBZmQRGWZ5g8j # qnKjRParSqMft1gvjuUTVgtWNZfgcLFSK5Wa0myrq8OPcgTGGsRgun+tnSS+IxDT # xVsAPH1OzvPjwomguByhUe/OcvUN0D5Wmp7xAgMBAAGjggGqMIIBpjAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFNoH7a2YDjOSwpkp6DHcmUS7J+0yMFQGA1UdEQRNMEukSTBHMS0wKwYDVQQL # EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxFjAUBgNVBAUT # DTIzMDAxMis1MDc1NjkwHwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEw # YAYDVR0fBFkwVzBVoFOgUYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w # cy9jcmwvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # bDBtBggrBgEFBQcBAQRhMF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9z # b2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmcl # MjBQQ0ElMjAyMDI0LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IC # AQAUnEqhaRXe0T3hIJjvdQErEkrA/7bByjn6t5IArODkkRjzkYwtKMc2yYj2quaN # rLutWw2YZcngKPy1b71YyDJQTy4NDRwaSh9Tw5thrk3NmcPrAHia5vtcBJ1CgtKK # 7mQbIcQ22d/N3813ayCDDFewu1+jsZmX+r/aTEqaOM4TVxVtRSkuCy8nAXKuChOK # Li/zA4XuH8iEYqIsj2YoNaeSxVmeGiERXpKdo3dDmYi0kO5w2D8VS4c3+9h6gElY # BaAAg/dYErBg27qT3vv0zRDJhJufvCNylA8S7/+8H5E/PV5cng6na9VV/w9OV3qu # uND6zdGa2EX38Glp50F9AIQk3p2xXmcvorDeM4XJ7UlWYBi6g80J1SSOQnInCYFE # msfUNn3+1AaTJKSJL83quKArTac2pKhu0Yzzzrzo6HrsRiQKzpnRBb1/dMa6P3hz # 75XbMRBctNsFhZC07WCmjExdLg2eHW5uV0TY8D5+6wozJf7vF3+WHkYPO85Z+BC6 # U4FkNbYNycZ9cE4j1tXRdyDCfml6c0HWPHjNVDObrv9lKt3qUqFpX38VCqVCyNOO # 1UcXfQiVjJw32U2WUKZjt/neJKHEBsm9kFsLuWzkQ53+qcaSaytmsCnk2gOglrlD # 5d3kKyvvAw+rzm0lT8K38P6PLxfZQHhu4W8dV7Av8N2ZmDCCBr0wggSloAMCAQIC # EzMAAAA5O7Y3Gb8GHWcAAAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS # b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoX # DTM2MDMyMjIyMTMwNFowVzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQ # Q0EgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeq # lRYHNa265v4IY9fH8TKhemHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo # 0dtS/EW6I/yEL/bLSY8hKpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATv # QVL4tcf03aTycsz8QeCdM0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a # 1uv1zerOYMnsneRRwCbpyW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1 # FyQfK0fVkaya8SmVHQ/tOf23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfO # GSWHIIV4YrTJTT6PNty5REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7 # ttOu1bVnXfHaqPYl2rPs20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJ # uz2MXMCt7iw7lFPG9LXKGjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxS # CwyoGIq0PhaA7Y+VPct5pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOm # VQop36wUVUYklUy++vDWeEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3 # SkE/xIkgpfl22MM1itkZ35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8E # BAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPX # LQaUEggxMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMB # Af8wHwYDVR0jBBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBP # oE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAw # TgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMv # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOC # AgEAFJQfOChP7onn6fLIMKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D # 5W4wMwYeLystcEqfkjz4NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBY # nbu0+THSuVHTe0VTTPVhily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSI # vgn0JksVBVMYVI5QFu/qhnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6 # aR9y34aiM1qmxaxBi6OUnyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4w # PKC5OmHm1DQIt/MNokbbH3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7 # RTX8AdBPo0I6OEojf39zuFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK # /fg8B2qjW88MT/WF5V5uvZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSK # YBv0VisCzfxgeU+dquXW9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkw # YTu/9dLeH2pDqeJZAABVDWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVT # Ql0v4q8J/AUmQN5W4n101cY2L4A7GTQG1h32HHAvfQESWP0xghn7MIIZ9wIBATBu # MFcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # KDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIc # +s3Fm+gvfsQAAAAAAhwwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwG # CisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZI # hvcNAQkEMSIEIGjYbqM6lxQ5NocMzqve44b7Kjj4Ht0Mf+nHOrCjo3OcMEIGCisG # AQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAQmQKl3BumJQluAFxfoEN # V36jL7JLap2hVLXUSoIyIx0n469Wwd2PXE9vy9JMfaQQOavxppVxhEAQPHSzQvf8 # /FNSdAuEDKmGA8aDvclIgVp6NzErf0Qj+xMN1mLtCQWvZThzIsSlosdzmzMZHkSt # xIHrjGABKOxMp3ZO9WpOwsUk4egfqVigQa0npWD6VxhAMmcNLNVQhh/07MHYjBqb # ajrIe468OBtiv1MboQNM4TRLHUCJsglIA1qX2EcEhextXbTUNo1lJMtRgfkDiA81 # n92hdT2SpPklrgIZynjvWfaq0HDaTjtulpn4yhoyzpmNGGFiyD9uBHV0Xn7D9dFi # haGCF60wghepBgorBgEEAYI3AwMBMYIXmTCCF5UGCSqGSIb3DQEHAqCCF4YwgheC # AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFFMIIB # QQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCCJeVLMslc+IQSNGjn0 # ZET254pMwlkLZ31shX+QWHZKoAIGahBh/FQUGBMyMDI2MDYwOTIzNDIxNy45NjRa # MASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0 # ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjozNjA1LTA1RTAtRDk0NzElMCMG # A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEfswggcoMIIFEKAD # AgECAhMzAAACE7BDNWbPr5XoAAEAAAITMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgxN1oXDTI2MTExMzE4 # NDgxN1owgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTAr # BgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUG # A1UECxMeblNoaWVsZCBUU1MgRVNOOjM2MDUtMDVFMC1EOTQ3MSUwIwYDVQQDExxN # aWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEA9Jl64LoZxDINSFgz+9KS5Ozv5m548ePVzc9RXWe4T4/Mplfg # a4eq12RGdp5cVvnjde5vxfq2ax/jnu7vUW4rZN4mOUm5vh+kcYsQlYQ53FwgIB3n # EjcQHomrG3mZe/ozjFSAr6JbglKtIeAySPzAcFzyAer5lLNUHBEvQMM8BOjMyapC # vh0xsg4xKFcVEJQLKEfCGBffMZI/amutHFb3CUTZ7aVpG2KHEFUNlZ1vwMKvxXTP # RDnbwPGzyyqJJznfsLNHQ4vXt2ttS1PeCoGI0hN1Peq8yGsIXM9oocwC06DGNSM/ # 4LAx2uKvwmUn6NwLc0+tmvny6w28rZLejskRfnVWofEv1mWY0jHUnHrwSGBS8gVP # 9gcBs6P5g0OpJPMfxdUkHXRkcMPPW0hIP8NbW8W5Sup8HuwnSKbjpyAlGBUdM/V5 # rZb0sZmkn714r6ULGK+cLLAN6R3FhX6N0nj64F27LTK2BbS0pJZaXjo0eDNz1Qcx # eIFLUgF+RBsLYDn8E8cCkexK8Nlt3Gi9zJf55w6UfTZ+kwTMxMqFxh7+Tfx7+aBO # bZ+nx961AtiqAy7zVV69o/LWRdKPZdvZn9ESyGbTnPfjkBERv22prSlETlRwzP6b # mEVOKWLWVwxuwh7bUWUuUb1cj93zvttQYGQat5E9ALLJNmlvLKCskB7raLsCAwEA # AaOCAUkwggFFMB0GA1UdDgQWBBQTnhBKx+FryphQWMRipH49sMFAOjAfBgNVHSME # GDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRw # Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1l # LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsG # AQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p # Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMB # Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDAN # BgkqhkiG9w0BAQsFAAOCAgEAgmxaJrGqQ2D6UJhZ6Ql2SZFOaNuGbW3LzB+ES+l2 # BB1MJtBRSFdi/hVY33NpxsJQhQ5TLVp0DXYOkIoPQc17rH+IVhemO8jCt+U6I1TI # w6cR7c+tEo/Jjp6EqEU1c4/mraMjgHhQ+raC/OUAm98A1r4bIPHtsBmLROGmeE5X # LIFaBIZWHvh2COXITKObXVd5wGtJ1dZZdwaHACXF506jta+uoUdyzAeuNlTPLTrZ # 8nyhxGwk9Vh6eiDQ7CQMWSSa8DJS9PUXjeoi9vTdS7ZMXqu+tv6Qz3xtoBF5+YFK # 4uE+miGs90Fxm0VK2lWrmFhjkRl5zyoHOdwG7spNYkDomCPNWIudUQmQYKpt/Hss # pfcb+xpnWIDQdMzgE8pj1vpwLgWEnH7LtT4dZCeoDo9PK40RxBD8kKJ769ngkEwf # wCD2EX/MQk79eIvOhpnH12GuVByvaKZk5XZvqtPONNwr8q/qA3877IuWwWgnaeX+ # prpw0dZ/QLtbGGVrgP+TRQjt+2dcZA5P3X4LwANhiPsy0Ol4XCdj7OxBLFvOzsCP # DPaVnkp+dfDFG+NOBir7aqTJ68622pymg1V+6gc/1RvxC/wgvYyG033ecJqv0On0 # ZRNYr+i/OkwgA3HP1aLD0aHrEpw6lt0263iRkCvrcdcOW8w3jC8TJuaGWyC2S9jE # jzgwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEB # CwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYD # VQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAe # Fw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGm # TOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/H # ZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDc # wUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62A # W36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1w # jjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCG # MFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ # 1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP # 8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFz # ymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHz # NgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3 # xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsG # AQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/ # LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEG # DCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29m # dC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYB # BQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8G # A1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQw # VgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9j # cmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUF # BwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br # aS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQEL # BQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfC # cTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AF # vonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l # 9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn # 8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5m # O0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyx # TkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4 # S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9 # y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM # +Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhw # RNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDVjCCAj4C # AQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0 # ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjozNjA1LTA1RTAtRDk0NzElMCMG # A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIa # AxUAmBE8SCjxgjacmy8/VEdk7NxpR6aggYMwgYCkfjB8MQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T # dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO3SmeEwIhgPMjAyNjA2MDkx # MzU1NDVaGA8yMDI2MDYxMDEzNTU0NVowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA # 7dKZ4QIBADAHAgEAAgIBZTAHAgEAAgITWTAKAgUA7dPrYQIBADA2BgorBgEEAYRZ # CgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0G # CSqGSIb3DQEBCwUAA4IBAQBKVpbNP8KKgXqOW9UQ5dUSP2p2lJAWPxAnwkhW5osO # W0LsLnUWgKsYoAePKges0uN7mzoNRIUqlquGVCjJ8K4vqJdpaXE/knv5l116Cine # kuppJD41Aq8rRYALDDjKffyr6ArlbMsjWlxFN4jpjBzMOM/1AXsSBQ+xgpBkonrS # noyIX80B7R0j8b5pG1Z12Low8M6wQE12AlsUgk6Qb5mEhv4OYjL/dEEByhP6kX98 # ZunGrbVi369fEdJ45tX4ue8D2dUUncD+x2Qf8RCIRtdTsltkR7m09w1BQ/CwSuG6 # 6GIy37ITgWXqVh3thlRKR+XMt4tVPLqnWy1B5Cdm8vGzMYIEDTCCBAkCAQEwgZMw # fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd # TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAITsEM1Zs+vlegAAQAA # AhMwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB # BDAvBgkqhkiG9w0BCQQxIgQgHQt5tn2gbeWt6l669LBX6qas7aUzVEQ1eNEJgB0y # uqEwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCDM4QltFIUz8J4DjAzP4nVo # dZvQxYGleUIfp86Oa5xYaDCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD # QSAyMDEwAhMzAAACE7BDNWbPr5XoAAEAAAITMCIEINHs5aLatkZiOBLnf5sb6Xka # TL4tJASeQ72ULXa/epmmMA0GCSqGSIb3DQEBCwUABIICAKENdat7Ym4n1zLE1m6j # YAlGO5qpho60uqx866moRGxCfKmf/lHzGBIyVpvlFlAj7l07rUvXc/Jn/LkYLCqI # HvO3u/7iZBbhm1+vGKYv7VKbGqxuHc0JFW1wQ35b37fSRmdU/OUvq1GndvhNmHyj # s+mpSzBXrHnjuyAh2ETCZJEjqdFTx7F6Vs7CH5wMi6JX7VLV3ZckXB7GaslHUnY8 # kCEayv4md3a03bvYa2kClERhJYYwQ4imNYl3iBHdG6Zblb03PcdN+zIhJCUosyU7 # HEW/jd6MIFja5twe8oFBHEvy2eqgQaiMtS1JUJfQgAiQlF1XL1BmS9zdpJvD9I/s # fFQJ3w/al2UzA+Hd18PyMEEADdLgmEwa2hUkCc+97QVW7zUxti88EjmhrQlBeJbk # 3i0mHylltTNCoCim3UAlLTCx2KaXNLsOl5sWXKAPEFR57f+PnatzOlX5Gb/z3VCc # fQ3d+mfDGZ/vo+q7d/5PAha2jkeMC8G0QOUlifBUIUKPEy63d4+i5YFw0uFGi2ep # Zy4O2XVI7OMERPIXcGGiSZ+Kh5txFF8HRk59+jL1e44EhGE2fy0d3Xe1ukxzBdvB # 0jhJ3eoXHZ0vbIDxWdUZ40zw0q8W6H1JVVU7vpsRqcbkZZiaiuwLCYqWsEjwrA94 # ptRtYF2TH3QyECal1edBhrLm # SIG # End signature block |