####################################################################################################
## Script Name: PoshCode Module
## Created On:
## Author: Joel 'Jaykul' Bennett
## File: PoshCode.psm1
## Usage:
## Version: 3.10
## Purpose: Provides cmdlets for working with scripts from the PoshCode Repository:
## Get-PoshCodeUpgrade - get the latest version of this script from the PoshCode server
## Get-PoshCode - Search for and download code snippets
## New-PoshCode - Upload new code snippets
## Get-WebFile - Download
## Requirements: PowerShell Version 2 CTP3 or later
## Last Updated: 07/29/2009
## History:
## 3.10 11/08/2009 - Fix a typo bug in Get-PoshCode
## 3.9 10/02/2009 - Put back the fixed NTFS Streams
## 3.8 08/04/2009 - Fixed PoshCodeUpgrade for CTP3+ and added secondary cert
## 3.7 07/29/2009 - Remove NTFS Streams
## 3.6 05/04/2009 - Documentation Rewrite
##
####################################################################################################
#requires -version 2.0
Set-StrictMode -Version Latest
$PoshCode = "http://PoshCode.org/" |
Add-Member -type NoteProperty -Name "UserName" -Value "Anonymous" -Passthru |
Add-Member -type ScriptProperty -Name "ScriptLocation" -Value {
$module = $null
Get-Module PoshCode | Select -expand Path -EA "SilentlyContinue" | Tee -var module
if(!$module) { # Try finding it by path, since it's not loaded as "PoshCode"
Get-Module | ? {$_.Name -match "^$([RegEx]::Escape($PsScriptRoot))"} | Select -expand Path
}
} -Passthru |
Add-Member -type ScriptProperty -Name "ModuleName" -Value {
if( Get-Module PoshCode ) { "PoshCode" } else {
Get-Module | ? {$_.Name -match "^$([RegEx]::Escape($PsScriptRoot))"} | Select -expand Name
}
} -Passthru |
Add-Member -type NoteProperty -Name "ScriptVersion" -Value 3.10 -Passthru |
Add-Member -type NoteProperty -Name "ApiVersion" -Value 1 -Passthru
function New-PoshCode {
<#
.SYNOPSIS
Uploads a script to PoshCode
.DESCRIPTION
Uploads code to the PowerShell Script Repository and returns the url for you.
.LINK
http://www.poshcode.org
.EXAMPLE
C:\PS>Get-Content MyScript.ps1 | New-PoshCode "An example for you" "This is just to show how to do it"
This command gets the content of MyScript.ps1 and passes it to New-Poshcode which then posts it to poshcode.org with the specified title and description.
.PARAMETER Path
Specifies the path to an item.
.PARAMETER Description
Sets the free-text summary of the script that will be displayed on the poshcode page for the script.
.PARAMETER Author
Specifies the author of the script that is being submitted.
.PARAMETER Language
Specifies the language of the script that is being submitted.
.PARAMETER Keep
Specifies how long to keep scripts on the poshcode.org site. Possible values are 'day', 'month', or 'forever'.
.PARAMETER Title
Specifies the title of the script that is being submitted.
.PARAMETER URL
Overrides the default PoshCode url, to allow posting to other Pastebin sites.
.NOTES
History:
v 3.1 - Fixed the $URL parameter so that it's settable again. *This* function should work on any pastebin site
v 3.0 - Renamed to New-PoshCode.
- Removed the -Permanent switch, since this is now exclusive to the permanent repository
v 2.1 - Changed some defaults
- Added "PermanentPosh" switch ( -P ) to switch the upload to the PowerShellCentral repository
v 2.0 - works with "pastebin" (including posh.jaykul.com/p/ and PowerShellCentral.com/scripts/)
v 1.0 - Worked with a special pastebin
#>
[CmdletBinding()]
PARAM(
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[string]
$Path
,
[Parameter(Position=5, Mandatory=$true)][string]$Description
,
[Parameter(Position=10)][string]$Author = $($PoshCode.UserName)
,
[Parameter(Position=15)][PoshCodeLanguage]$Language="posh"
,
[Parameter(Position=20, Mandatory=$false)]
[ValidateScript({ if($_ -match "^[dmf]") { return $true } else { throw "Please specify 'day', 'month', or 'forever'" } })]
[string]$Keep="f"
,
[Alias("BaseName,Name")]
[Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)]
[string]$Title
,
[Parameter(Mandatory=$false)][string]$url= $($PoshCode)
)
BEGIN {
$null = [Reflection.Assembly]::LoadWithPartialName("System.Web")
[string]$data = ""
[string]$meta = ""
if($language) {
$meta = "format=" + [System.Web.HttpUtility]::UrlEncode($language)
# $url = $url + "?" +$lang
} else {
$meta = "format=text"
}
# Note how simplified this is by
switch -regex ($Keep) {
"^d" { $meta += "&expiry=d" }
"^m" { $meta += "&expiry=m" }
"^f" { $meta += "&expiry=f" }
}
if($Description) {
$meta += "&descrip=" + [System.Web.HttpUtility]::UrlEncode($Description)
} else {
$meta += "&descrip="
}
$meta += "&poster=" + [System.Web.HttpUtility]::UrlEncode($Author)
function Send-PoshCode ($meta, $title, $data, $url= $($PoshCode)) {
$meta += "&paste=Send&posttitle=" + [System.Web.HttpUtility]::UrlEncode($Title)
$data = $meta + "&code2=" + [System.Web.HttpUtility]::UrlEncode($data)
$request = [System.Net.WebRequest]::Create($url)
$request.ContentType = "application/x-www-form-urlencoded"
$request.ContentLength = $data.Length
$request.Method = "POST"
$post = new-object IO.StreamWriter $request.GetRequestStream()
$post.Write($data,0,$data.Length)
$post.Flush()
$post.Close()
# $reader = new-object IO.StreamReader $request.GetResponse().GetResponseStream() ##,[Text.Encoding]::UTF8
# write-output $reader.ReadToEnd()
# $reader.Close()
write-output $request.GetResponse().ResponseUri.AbsoluteUri
$request.Abort()
}
}
PROCESS {
$EAP = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue"
if(Test-Path $Path -PathType Leaf) {
$ErrorActionPreference = $EAP
Write-Verbose $Path
Write-Output $(Send-PoshCode $meta $Title $([string]::join("`n",(Get-Content $Path))) $url)
} elseif(Test-Path $Path -PathType Container) {
$ErrorActionPreference = $EAP
Write-Error "Can't upload folders yet: $Path"
} else { ## Todo, handle folders?
$ErrorActionPreference = $EAP
if(!$data -and !$Title){
$Title = Read-Host "Give us a title for your post"
}
$data += "`n" + $Path
}
}
END {
if($data) {
Write-Output $(Send-PoshCode $meta $Title $data $url)
}
}
}
function Get-PoshCode {
<#
.SYNOPSIS
Search for and/or download scripts from PoshCode.org
.DESCRIPTION
Search PoshCode.org by search terms, and returns a list of results, Or download a specific script by ID and output the contents or save to file.
.LINK
http://www.poshcode.org
.EXAMPLE
C:\PS>Get-PoshCode Authenticode
This command searches the repository for scripts dealing with Authenticode, and list the results
Normally, you will take one of those IDs and do this:
.EXAMPLE
C:\PS>Get-PoshCode 456
This command will download the script with the ID of 456 and save it to file (based on it's name/contents)
.EXAMPLE
C:\PS>Get-PoshCode 456 -passthru
Thi command outputs the contents of that script into the pipeline, so eg:
(Get-PoshCode 456 -passthru) -replace "AuthenticodeSignature","SillySig"
.EXAMPLE
C:\PS>Get-PoshCode 456 $ProfileDir\Authenticode.psm1
This command downloads the script saving it as the name specified.
.EXAMPLE
C:\PS>Get-PoshCode SCOM | Get-PoshCode
This command searches the repository for all scripts about SCOM, and then downloads them!
.PARAMETER Path
Specifies the path to an item.
.PARAMETER Description
Sets the free-text summary of the script that will be displayed on the poshcode page for the script.
.PARAMETER Author
Specifies the author of the script that is being submitted.
.PARAMETER Language
Specifies the language of the script that is being submitted.
.PARAMETER Keep
Specifies how long to keep scripts on the poshcode.org site. Possible values are 'day', 'month', or 'forever'.
.PARAMETER Title
Specifies the title of the script that is being submitted.
.PARAMETER URL
.NOTES
All search terms are automatically surrounded with wildcards.
History:
v 3.10 - Fixed a typo
v 3.9 - Fixed and put back the Set-DownloadFlag code
v 3.7 - Removed the Set-DownloadFlag code because it was throwing on Windows 7:
"Attempted to read or write protected memory."
v 3.4 - Add "-Language" parameter to force PowerShell only, fix upgrade to leave INVALID as .psm1
v 3.2 - Add "-Upgrade" switch to cause the script to upgrade itself.
v 3.1 - Add "Huddled.PoshCode.ScriptInfo" to TypeInfo, so it can be formatted by a ps1xml
- Add ConvertTo-Module function to try to rename .ps1 scripts to .psm1
- Fixed exceptions thrown by searches which return no results
- Removed the auto-wildcards!!!!
NOTE: to get the same results as before you must now put * on the front and end of searches
This is so that searches on the website work the same as searches here...
My intention is to improve the website's search instead of leaving this here.
NOTE: the website currently will not search for words less than 4 characters long
v 3.0 - Working against the new RSS-based API
- And using ParameterSets, which work in CTP2
v 2.0 - Combined with Find-Poshcode into a single script
v 1.0 - Working against our special pastebin
#>
[CmdletBinding(DefaultParameterSetName="Download")]
PARAM(
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Search")]
[string]$Query
,
[Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="Download" )]
[int]$Id
,
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Upgrade")]
[switch]$Upgrade
,
[Parameter(Position=1, Mandatory=$false, ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[string]$SaveAs
,
[Parameter(Position=2, Mandatory=$false, ValueFromPipelineByPropertyName=$true)]
[ValidateSet('text','asp','bash','cpp','csharp','posh','vbnet','xml','all')]
[string]$Language="posh"
,
[switch]$InBrowser
,
[switch]$Passthru
,
[Parameter(Mandatory=$false)][string]$url= $($PoshCode)
)
PROCESS {
Write-Debug "ParameterSet Name: $($PSCmdlet.ParameterSetName)"
if($Language -eq 'all') { $Language = "" }
switch($PSCmdlet.ParameterSetName) {
"Search" {
$results = @(([xml](Get-WebFile "$($url)api$($PoshCode.ApiVersion)/$($query)&lang=$($Language)" -passthru)).rss.channel.GetElementsByTagName("item"))
if($results.Count -eq 0 ) {
Write-Host "Zero Results for '$query'" -Fore Red -Back Black
}
else {
$results | Select @{ n="Id";e={$($_.link -replace $url,'') -as [int]}},
@{n="Title";e={$_.title}},
@{n="Author";e={$_.creator}},
@{n="Date";e={$_.pubDate }},
@{n="Link";e={$_.guid.get_InnerText() }},
@{n="Web";e={$_.Link}},
@{n="Description";e={"$($_.description.get_InnerText())`n" }} |
ForEach { $_.PSObject.TypeNames.Insert( 0, "Huddled.PoshCode.ScriptInfo" ); $_ }
}
}
"Download" {
if(!$InBrowser) {
if($Passthru) {
Get-WebFile "$($url)?dl=$id" -Passthru
}
elseif($SaveAs) {
Get-WebFile "$($url)?dl=$id" -fileName $SaveAs | ConvertTo-Module | Set-DownloadFlag -Passthru
}
else {
Get-WebFile "$($url)?dl=$id" | ConvertTo-Module | Set-DownloadFlag -Passthru
}
}
else {
[Diagnostics.Process]::Start( "$($url)$id" )
}
}
"Upgrade" {
Get-PoshCodeUpgrade
}
}
}
}
function Get-PoshCodeUpgrade {
<#
.SYNOPSIS
Downloads a new PoshCode module and archives the old version(s).
.DESCRIPTION
.LINK
http://www.poshcode.org
.EXAMPLE
C:\PS>Get-PoshCodeUpgrade
This command gets the most recent version of the PoshCode module
.INPUTTYPE
.RETURNVALUE
.COMPONENT
.ROLE
.FUNCTIONALITY
.NOTES
History:
v3.9 - Fixed and put back the Remove-DownloadFlag
v3.8 - Switched "Add-Module" to "Import-Module" to make it CTP3+ compatible.
v3.7 - Removed the Set-DownloadFlag code because it was throwing on Windows 7:
"Attempted to read or write protected memory."
v3.3 - Removes old versions, and checks the signature.
v3.2 - First script version with Upgrade function
#>
$VersionFile = [IO.Path]::ChangeExtension( $PoshCode.ScriptLocation,
("{0}{1}" -f $PoshCode.ScriptVersion, [IO.Path]::GetExtension($PoshCode.ScriptLocation)))
# Copy it to make sure we don't loose it
Copy-Item $PoshCode.ScriptLocation $VersionFile
# Remove old ones ...
Remove-Item ( [IO.Path]::ChangeExtension( $PoshCode.ScriptLocation,
".*$([IO.Path]::GetExtension( $($PoshCode.ScriptLocation) ))")
) -exclude ([IO.Path]::GetFileName($VersionFile)) -Confirm
# Finally, get the new one
$NewFile = Get-WebFile "$($PoshCode)PoshCode.psm1" -fileName (
[IO.Path]::ChangeExtension( $PoshCode.ScriptLocation, ".INVALID.ps1"))
if( Test-Signature -File $NewFile )
{
Move-Item $NewFile $PoshCode.ScriptLocation -Force -passthru | Remove-DownloadFlag -Passthru
Import-Module $($PoshCode.ModuleName) -Force
}
else {
Write-Error "Signature is Not Valid on new version."
Move-Item $NewFile ([IO.Path]::ChangeExtension( $PoshCode.ScriptLocation, ".INVALID.psm1"))
Get-Item ([IO.Path]::ChangeExtension( $PoshCode.ScriptLocation, ".INVALID.psm1"))
}
}
## Test-Signature - Returns true if the signature is valid OR is signed by:
## "4F8842037D878C1FCDC6FD1313B200449716C353" OR "7DEFA3C6C2138C05AAA135FB8096332DEB9603E1"
function Test-Signature {
[CmdletBinding(DefaultParameterSetName="File")]
PARAM (
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Signature")]
# We can't actually require the type, or we won't be able to check the fake ones from Joel's Authenticode module
# [System.Management.Automation.Signature]
$Signature
, [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="File")]
[System.IO.FileInfo]
$File
)
PROCESS {
if($File -and (Test-Path $File -PathType Leaf)) {
$Signature = Get-AuthenticodeSignature $File
}
if(!$Signature) { return $false } else {
$result = $false;
try {
$result = ((($Signature.Status -eq "UnknownError") -and $Signature.SignerCertificate -and
(($Signature.SignerCertificate.Thumbprint -eq "4F8842037D878C1FCDC6FD1313B200449716C353") -or
($Signature.SignerCertificate.Thumbprint -eq "7DEFA3C6C2138C05AAA135FB8096332DEB9603E1"))
) -or $Signature.Status -eq "Valid" )
} catch { } finally { return $result }
}
}
}
filter ConvertTo-Module {
$oldFile = $_
if( ([IO.Path]::GetExtension($oldFile) -eq ".ps1") -and
[Regex]::Match( [IO.File]::ReadAllText($oldFile),
"^[^#]*Export-ModuleMember.*", "MultiLine").Success )
{
$fileName = [IO.Path]::ChangeExtension($oldFile, ".psm1")
Move-Item $oldFile $fileName -Force
Get-Item $fileName
} else { Get-Item $oldFile }
}
## Get-WebFile (aka wget for PowerShell)
##############################################################################################################
## Downloads a file or page from the web
## History:
## v3.9 - Fixed and replaced the Set-DownloadFlag
## v3.7 - Removed the Set-DownloadFlag code because it was throwing on Windows 7:
## "Attempted to read or write protected memory."
## v3.6.6 Add UserAgent calculation and parameter
## v3.6.5 Add file-name guessing and cleanup
## v3.6 - Add -Passthru switch to output TEXT files
## v3.5 - Add -Quiet switch to turn off the progress reports ...
## v3.4 - Add progress report for files which don't report size
## v3.3 - Add progress report for files which report their size
## v3.2 - Use the pure Stream object because StreamWriter is based on TextWriter:
## it was messing up binary files, and making mistakes with extended characters in text
## v3.1 - Unwrap the filename when it has quotes around it
## v3 - rewritten completely using HttpWebRequest + HttpWebResponse to figure out the file name, if possible
## v2 - adds a ton of parsing to make the output pretty
## added measuring the scripts involved in the command, (uses Tokenizer)
##############################################################################################################
function Get-WebFile {
param(
$url = (Read-Host "The URL to download"),
$fileName = $null,
[switch]$Passthru,
[switch]$quiet,
[string]$UserAgent = "PoshCode/$($PoshCode.ScriptVersion)"
)
Write-Verbose "Downloading '$url'"
$req = [System.Net.HttpWebRequest]::Create($url);
$req.UserAgent = $(
"{0} (PowerShell {1}; .NET CLR {2}; {3}; http://PoshCode.org)" -f $UserAgent,
$(if($Host.Version){$Host.Version}else{"1.0"}),
[Environment]::Version,
[Environment]::OSVersion.ToString().Replace("Microsoft Windows ", "Win")
)
$res = $req.GetResponse();
if($fileName -and !(Split-Path $fileName)) {
$fileName = Join-Path (Convert-Path (Get-Location -PSProvider "FileSystem")) $fileName
}
elseif((!$Passthru -and ($fileName -eq $null)) -or (($fileName -ne $null) -and (Test-Path -PathType "Container" $fileName)))
{
[string]$fileName = ([regex]'(?i)filename=(.*)$').Match( $res.Headers["Content-Disposition"] ).Groups[1].Value
$fileName = $fileName.trim("\/""'")
$ofs = ""
$fileName = [Regex]::Replace($fileName, "[$([Regex]::Escape(""$([System.IO.Path]::GetInvalidPathChars())$([IO.Path]::AltDirectorySeparatorChar)$([IO.Path]::DirectorySeparatorChar)""))]", "_")
$ofs = " "
if(!$fileName) {
$fileName = $res.ResponseUri.Segments[-1]
$fileName = $fileName.trim("\/")
if(!$fileName) {
$fileName = Read-Host "Please provide a file name"
}
$fileName = $fileName.trim("\/")
if(!([IO.FileInfo]$fileName).Extension) {
$fileName = $fileName + "." + $res.ContentType.Split(";")[0].Split("/")[1]
}
}
$fileName = Join-Path (Convert-Path (Get-Location -PSProvider "FileSystem")) $fileName
}
if($Passthru) {
$encoding = [System.Text.Encoding]::GetEncoding( $res.CharacterSet )
[string]$output = ""
}
if($res.StatusCode -eq 200) {
[int]$goal = $res.ContentLength
$reader = $res.GetResponseStream()
if($fileName) {
$writer = new-object System.IO.FileStream $fileName, "Create"
}
[byte[]]$buffer = new-object byte[] 4096
[int]$total = [int]$count = 0
do
{
$count = $reader.Read($buffer, 0, $buffer.Length);
if($fileName) {
$writer.Write($buffer, 0, $count);
}
if($Passthru){
$output += $encoding.GetString($buffer,0,$count)
} elseif(!$quiet) {
$total += $count
if($goal -gt 0) {
Write-Progress "Downloading $url" "Saving $total of $goal" -id 0 -percentComplete (($total/$goal)*100)
} else {
Write-Progress "Downloading $url" "Saving $total bytes..." -id 0
}
}
} while ($count -gt 0)
$reader.Close()
if($fileName) {
$writer.Flush()
$writer.Close()
}
if($Passthru){
$output
}
}
$res.Close();
if($fileName) {
Set-DownloadFlag $fileName -PassThru
}
}
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
function Set-DownloadFlag {
<#
.Synopsis
Sets the ZoneTransfer flag which marks a file as being downloaded from the internet.
.Description
Creates a Zone.Identifier alternate data stream (on NTFS file systems) and writes the ZoneTransfer marker
.Parameter Path
The file you wish to block
.Parameter Passthru
If set, outputs the FileInfo object
.Parameter ZoneId
THe Zone you want to mark the file with. Defaults to 4
#>
[CmdletBinding()]
PARAM (
[Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[string]
$Path
,
[Parameter(Position=1, Mandatory=$false)]
[ZoneIdentifier]$Zone = "Untrusted"
,
[Switch]$Passthru
)
PROCESS {
$FS = new-object PoshCodeNTFS.FileStreams($Path)
$null = $fs.add('Zone.Identifier')
$stream = $fs.Item('Zone.Identifier').open()
$sw = [System.IO.streamwriter]$stream
$Sw.writeline('[ZoneTransfer]')
$sw.writeline("ZoneID=$([Int]$zone)")
$sw.close()
$stream.close()
if($Passthru){ Get-ChildItem $Path }
}
}
function Remove-DownloadFlag {
<#
.Synopsis
Removes the ZoneTransfer flag which marks a file as being downloaded from the internet.
.Description
Deletes the Zone.Identifier alternate data stream (on NTFS file systems)
.Parameter Path
The file you wish to block
.Parameter Passthru
If set, outputs the FileInfo object
#>
[CmdletBinding()]
PARAM (
[Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[string]
$Path
,
[Switch]$Passthru
)
PROCESS {
Remove-Stream -Path $Path -Name 'Zone.Identifier'
if($Passthru){ Get-ChildItem $Path }
}
}
function Get-DownloadFlag {
<#
.Synopsis
Verify whether the ZoneTransfer flag is set (marking this file as one downloaded from the internet).
.Description
Reads the Zone.Identifier alternate data stream (on NTFS file systems)
.Parameter Path
The file you wish to check the ZoneTransfer flag on
#>
[CmdletBinding()]
PARAM (
[Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[string]
$Path
)
Process {
$FS = new-object PoshCodeNTFS.FileStreams($Path)
if(!$fs.Item('Zone.Identifier') ) {
Write-Warning "Zone.Identifier not set on $Path (no Download Flag). This is the equivalent of a 'Trusted' flag."
return
}
$reader = [System.IO.streamreader]$fs.Item('Zone.Identifier').open()
try {
do {
$line = $reader.ReadLine()
} until (!$line -OR $line -eq '[ZoneTransfer]')
$out = new-object PSObject
while($zone = $reader.ReadLine()) {
$zone = $zone -split "="
if($zone.Count -lt 2) { break }
Add-Member -in $out -Type NoteProperty -Name $zone[0] -value ([ZoneIdentifier]$zone[1])
}
$out
} finally {
$reader.close()
}
}
}
function Test-DownloadFlag {
<#
.Synopsis
Verify whether the ZoneTransfer flag is set (marking this file as one downloaded from the internet).
.Description
Reads the Zone.Identifier alternate data stream (on NTFS file systems)
.Parameter Path
The file you wish to check the ZoneTransfer flag on
#>
[CmdletBinding()]
PARAM (
[Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[string]
$Path
)
Process {
Get-ChildItem $Path | Select Name, @{N="Downloaded";E={ [bool]((new-object PoshCodeNTFS.FileStreams($_)).Item('Zone.Identifier')) } }, FullName, Length
}
}
function Normalize-StreamName {
PARAM($Path,$StreamName)
if(!$StreamName -and !(Test-Path $Path -EA 0)) {
$Path, $Segment, $StreamName = $Path -split ":"
if($StreamName -or (Test-Path ($Path,$Segment -join ":") -EA 0)) {
$Path = $Path,$Segment -join ":"
} else {
$StreamName = $Segment
}
}
return $Path,$StreamName
}
function Get-Stream {
<#
.Synopsis
Get the list of alternate NTFS Streams
.Description
Reads the named alternate data stream on NTFS file systems.
.Parameter Path
The file you wish to read from. You may include the stream name in the format:
C:\Path\File.extension:stream name
.Parameter Stream
The name of the stream you wish to read from. If you pass this as a separate parameter, you should NOT include it in the Path.
#>
[CmdletBinding()]
PARAM (
[Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[string]
$Path
,
[Parameter(Position=1,Mandatory=$false)]
[Alias("Name")][string]$StreamName
,
[Parameter()]
[Switch]$Force
)
Process {
$Path,$Stream = Normalize-StreamName $Path $StreamName
Write-Verbose "Path: $Path"
Write-Verbose "Stream: $Stream"
ForEach($file in Get-ChildItem $Path) {
$FS = new-object PoshCodeNTFS.FileStreams($file)
Write-Verbose "File: $File"
if(!$Stream) {
$FS
} else {
$FS | Where { $_.StreamName -like $Stream } | Tee -Var Output
if($Force -and -not $Output) {
$FS.add($Stream) > $null
$FS.Item($Stream)
}
}
}
}
}
function Get-StreamContent {
<#
.Synopsis
Get the contents of a named NTFS Stream
.Description
Reads the named alternate data stream (on NTFS file systems)
.Parameter StreamInfo
A StreamInfo object for the stream you want to get the content of.
.Parameter Path
The file to read from. You may include the stream name in the format:
C:\Path\File.extension:stream name
.Parameter StreamName
The name of the stream you wish to read from. If you pass this as a separate parameter, you should NOT include it in the Path.
#>
[CmdletBinding(DefaultParameterSetName="ByStream")]
PARAM (
[Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="ByName")]
[Alias("FullName")][string]$Path
,
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="ByStream")]
[PoshCodeNTFS.StreamInfo]$StreamInfo
,
[Parameter(Position=1, Mandatory=$false, ParameterSetName="ByName")]
[Alias("Name")][string]$StreamName
)
Process {
switch($PSCmdlet.ParameterSetName) {
"ByName" {
Get-Stream $Path $StreamName | Get-StreamContent
}
"ByStream" {
$fileStream = $StreamInfo.open()
$reader = [System.IO.StreamReader]$fileStream
$reader.ReadToEnd()
$fileStream.close()
}
}
}
}
function Remove-Stream {
<#
.Synopsis
Remove a stream from a file (or, delete the file).
.Description
Deletes the named alternate data stream (on NTFS file systems)
.Parameter StreamInfo
A StreamInfo object for the stream you want to get the content of.
.Parameter Path
The file to delete from. You may include the stream name in the format:
"C:\Path\File.extension:stream name"
.Parameter StreamName
The name of the stream you wish to remove. If you pass this as a separate parameter, you should NOT include it in the Path.
#>
[CmdletBinding(DefaultParameterSetName="ByStream")]
PARAM (
[Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="ByName")]
[Alias("FullName")][string]$Path
,
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="ByStream")]
[PoshCodeNTFS.StreamInfo]$StreamInfo
,
[Parameter(Position=1, Mandatory=$false, ParameterSetName="ByName")]
[Alias("Name")][string]$StreamName
)
Process {
switch($PSCmdlet.ParameterSetName) {
"ByName" {
foreach($StreamInfo in Get-Stream $Path $StreamName) {
Write-Verbose $($StreamInfo |Out-String)
$StreamInfo.Delete() > $null
}
}
"ByStream" {
$StreamInfo.Delete() > $null
}
}
}
}
function Set-StreamContent {
<#
.Synopsis
Set the contents of a named NTFS Stream
.Description
Sets the content of the named alternate data stream (on NTFS file systems)
.Parameter StreamInfo
A StreamInfo object for the stream you want to set the content of.
.Parameter Path
The file to set content on. You may include the stream name in the format:
"C:\Path\File.extension:stream name"
.Parameter StreamName
The name of the stream you wish to set. If you pass this as a separate parameter, you should NOT include it in the Path.
#>
[CmdletBinding(DefaultParameterSetName="ByStream")]
PARAM (
[Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="ByName")]
[Alias("FullName")][string]$Path
,
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="ByStream")]
[PoshCodeNTFS.StreamInfo]$StreamInfo
,
[Parameter(Position=1, Mandatory=$false, ParameterSetName="ByName")]
[Alias("Name")][string]$StreamName
,
[Parameter(Position=2, Mandatory=$true)]
[String]$Value
)
Process {
switch($PSCmdlet.ParameterSetName) {
"ByName" {
Write-Verbose "Path: $Path"
Get-Stream $Path $StreamName -Force | Set-StreamContent -Value $Value
}
"ByStream" {
$writer =[System.IO.streamwriter] $StreamInfo.Open()
$writer.Write($value)
$writer.close()
}
}
}
}
Add-Type -TypeDefinition @'
public enum PoshCodeLanguage {
asp,
bash,
csharp,
posh,
vbnet,
xml,
text
}
'@
Add-Type -TypeDefinition @'
public enum ZoneIdentifier {
Trusted = 1,
Intranet = 2,
Internet = 3,
Untrusted = 4
}
'@
Add-Type -TypeDefinition @'
using System;
using System.IO;
using System.Collections;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
///
///Encapsulates access to alternative data streams of an NTFS file.
///Adapted from a C++ sample by Dino Esposito,
///http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/ntfs5.asp
///
namespace PoshCodeNTFS {
///
/// Wraps the API functions, structures and constants.
///
internal class Kernel32
{
public const char STREAM_SEP = ':';
public const int INVALID_HANDLE_VALUE = -1;
public const int MAX_PATH = 256;
[Flags()] public enum FileFlags : uint
{
WriteThrough = 0x80000000,
Overlapped = 0x40000000,
NoBuffering = 0x20000000,
RandomAccess = 0x10000000,
SequentialScan = 0x8000000,
DeleteOnClose = 0x4000000,
BackupSemantics = 0x2000000,
PosixSemantics = 0x1000000,
OpenReparsePoint = 0x200000,
OpenNoRecall = 0x100000
}
[Flags()] public enum FileAccessAPI : uint
{
GENERIC_READ = 0x80000000,
GENERIC_WRITE = 0x40000000
}
///
/// Provides a mapping between a System.IO.FileAccess value and a FileAccessAPI value.
///
/// The value to map.
/// The value.
public static FileAccessAPI Access2API(FileAccess Access)
{
FileAccessAPI lRet = 0;
if ((Access & FileAccess.Read)==FileAccess.Read) lRet |= FileAccessAPI.GENERIC_READ;
if ((Access & FileAccess.Write)==FileAccess.Write) lRet |= FileAccessAPI.GENERIC_WRITE;
return lRet;
}
[StructLayout(LayoutKind.Sequential)] public struct LARGE_INTEGER
{
public int Low;
public int High;
public long ToInt64()
{
return (long)High * 4294967296 + (long)Low;
}
}
[StructLayout(LayoutKind.Sequential)] public struct WIN32_STREAM_ID
{
public int dwStreamID;
public int dwStreamAttributes;
public LARGE_INTEGER Length;
public int dwStreamNameLength;
}
[DllImport("kernel32")] public static extern SafeFileHandle CreateFile(string Name, FileAccessAPI Access, FileShare Share, int Security, FileMode Creation, FileFlags Flags, int Template);
[DllImport("kernel32")] public static extern bool DeleteFile(string Name);
[DllImport("kernel32")] public static extern bool CloseHandle(SafeFileHandle hObject);
[DllImport("kernel32")] public static extern bool BackupRead(SafeFileHandle hFile, IntPtr pBuffer, int lBytes, ref int lRead, bool bAbort, bool bSecurity, ref int Context);
[DllImport("kernel32")] public static extern bool BackupRead(SafeFileHandle hFile, ref WIN32_STREAM_ID pBuffer, int lBytes, ref int lRead, bool bAbort, bool bSecurity, ref int Context);
[DllImport("kernel32")] public static extern bool BackupSeek(SafeFileHandle hFile, int dwLowBytesToSeek, int dwHighBytesToSeek, ref int dwLow, ref int dwHigh, ref int Context);
}
///
/// Encapsulates a single alternative data stream for a file.
///
public class StreamInfo
{
private FileStreams _parent;
private string _name;
private long _length;
internal StreamInfo(FileStreams Parent, string Name, long Length)
{
_parent = Parent;
_name = Name;
_length = Length;
}
///
/// The name of the file.
///
public string FileName
{
get { return System.IO.Path.GetFileName(_parent.FileName); }
}
///
/// The name of the stream.
///
public string StreamName
{
get { return _name; }
}
///
/// The length (in bytes) of the stream.
///
public long Length
{
get { return _length; }
}
public override string ToString()
{
if(String.IsNullOrEmpty(_name)) {
return _parent.FileName;
} else {
return String.Format("{1}{0}{2}", Kernel32.STREAM_SEP, _parent.FileName, _name);
}
}
public override bool Equals(Object o)
{
if (o is StreamInfo)
{
StreamInfo f = (StreamInfo)o;
return (f._name.Equals(_name) && f._parent.Equals(_parent));
}
else if (o is string)
{
return ((string)o).Equals(ToString());
}
else
return base.Equals(o);
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
#region Open
///
/// Opens or creates the stream in read-write mode, with no sharing.
///
/// A wrapper for the stream.
public FileStream Open()
{
return Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
}
///
/// Opens or creates the stream in read-write mode with no sharing.
///
/// The action for the stream.
/// A wrapper for the stream.
public FileStream Open(FileMode Mode)
{
return Open(Mode, FileAccess.ReadWrite, FileShare.None);
}
///
/// Opens or creates the stream with no sharing.
///
/// The action for the stream.
/// The level for the stream.
/// A wrapper for the stream.
public FileStream Open(FileMode Mode, FileAccess Access)
{
return Open(Mode, Access, FileShare.None);
}
///
/// Opens or creates the stream.
///
/// The action for the stream.
/// The level for the stream.
/// The level for the stream.
/// A wrapper for the stream.
public FileStream Open(FileMode Mode, FileAccess Access, FileShare Share)
{
try
{
if(String.IsNullOrEmpty(_name)) {
return new FileStream(ToString(), Mode, Access, Share);
} else {
SafeFileHandle hFile = Kernel32.CreateFile(ToString() + Kernel32.STREAM_SEP + "$DATA", Kernel32.Access2API(Access), Share, 0, Mode, 0, 0);
return new FileStream(hFile, Access);
}
}
catch
{
return null;
}
}
#endregion
#region Delete
///
/// Deletes the stream from the file.
///
/// A value: true if the stream was deleted, false if there was an error.
public bool Delete()
{
return Kernel32.DeleteFile(ToString());
}
#endregion
}
///
/// Encapsulates the collection of alternative data streams for a file.
/// A collection of objects.
///
public class FileStreams : CollectionBase
{
private FileInfo _file;
#region Constructors
public FileStreams(string File)
{
_file = new FileInfo(File);
initStreams();
}
public FileStreams(FileInfo file)
{
_file = file;
initStreams();
}
///
/// Reads the streams from the file.
///
private void initStreams()
{
base.List.Add(new StreamInfo(this,String.Empty,_file.Length));
//Open the file with backup semantics
SafeFileHandle hFile = Kernel32.CreateFile(_file.FullName, Kernel32.FileAccessAPI.GENERIC_READ, FileShare.Read, 0, FileMode.Open, Kernel32.FileFlags.BackupSemantics, 0);
if (hFile.IsInvalid) return;
try
{
Kernel32.WIN32_STREAM_ID sid = new Kernel32.WIN32_STREAM_ID();
int dwStreamHeaderLength = Marshal.SizeOf(sid);
int Context = 0;
bool Continue = true;
while (Continue)
{
//Read the next stream header
int lRead = 0;
Continue = Kernel32.BackupRead(hFile, ref sid, dwStreamHeaderLength, ref lRead, false, false, ref Context);
if (Continue && lRead == dwStreamHeaderLength)
{
if (sid.dwStreamNameLength>0)
{
//Read the stream name
lRead = 0;
IntPtr pName = Marshal.AllocHGlobal(sid.dwStreamNameLength);
try
{
Continue = Kernel32.BackupRead(hFile, pName, sid.dwStreamNameLength, ref lRead, false, false, ref Context);
char[] bName = new char[sid.dwStreamNameLength];
Marshal.Copy(pName,bName,0,sid.dwStreamNameLength);
//Name is of the format ":NAME:$DATA\0"
string sName = new string(bName);
int i = sName.IndexOf(Kernel32.STREAM_SEP, 1);
if (i>-1) sName = sName.Substring(1, i-1);
else
{
//This should never happen.
//Truncate the name at the first null char.
i = sName.IndexOf('\0');
if (i>-1) sName = sName.Substring(1, i-1);
}
//Add the stream to the collection
base.List.Add(new StreamInfo(this,sName,sid.Length.ToInt64()));
}
finally
{
Marshal.FreeHGlobal(pName);
}
}
//Skip the stream contents
int l = 0; int h = 0;
Continue = Kernel32.BackupSeek(hFile, sid.Length.Low, sid.Length.High, ref l, ref h, ref Context);
}
else break;
}
}
finally
{
Kernel32.CloseHandle(hFile);
}
}
#endregion
#region File
///
/// Returns the object for the wrapped file.
///
public FileInfo FileInfo
{
get { return _file; }
}
///
/// Returns the full path to the wrapped file.
///
public string FileName
{
get { return _file.FullName; }
}
///
/// Returns the length of the main data stream, in bytes.
///
public long Length {
get {return _file.Length;}
}
///
/// Returns the length of all streams for the file, in bytes.
///
public long FullLength
{
get
{ // don't initialize with "this.Length" anymore, because we include the default stream now
long length = 0;
foreach (StreamInfo s in this) length += s.Length;
return length;
}
}
#endregion
#region Open
///
/// Opens or creates the default file stream.
///
///
public FileStream Open()
{
return new FileStream(_file.FullName, FileMode.OpenOrCreate);
}
///
/// Opens or creates the default file stream.
///
/// The for the stream.
///
public FileStream Open(FileMode Mode)
{
return new FileStream(_file.FullName, Mode);
}
///
/// Opens or creates the default file stream.
///
/// The for the stream.
/// The for the stream.
///
public FileStream Open(FileMode Mode, FileAccess Access)
{
return new FileStream(_file.FullName, Mode, Access);
}
///
/// Opens or creates the default file stream.
///
/// The for the stream.
/// The for the stream.
/// The for the stream.
///
public FileStream Open(FileMode Mode, FileAccess Access, FileShare Share)
{
return new FileStream(_file.FullName, Mode, Access, Share);
}
#endregion
#region Delete
///
/// Deletes the file, and all alternative streams.
///
public void Delete()
{
for (int i=base.List.Count;i>0;i--)
{
base.List.RemoveAt(i);
}
_file.Delete();
}
#endregion
#region Collection operations
///
/// Add an alternative data stream to this file.
///
/// The name for the stream.
/// The index of the new item.
public int Add(string Name)
{
StreamInfo FSI = new StreamInfo(this, Name, 0);
int i = base.List.IndexOf(FSI);
if (i==-1) i = base.List.Add(FSI);
return i;
}
///
/// Removes the alternative data stream with the specified name.
///
/// The name of the string to remove.
public void Remove(string Name)
{
StreamInfo FSI = new StreamInfo(this, Name, 0);
int i = base.List.IndexOf(FSI);
if (i>-1) base.List.RemoveAt(i);
}
///
/// Returns the index of the specified object in the collection.
///
/// The object to find.
/// The index of the object, or -1.
public int IndexOf(StreamInfo FSI)
{
return base.List.IndexOf(FSI);
}
///
/// Returns the index of the object with the specified name in the collection.
///
/// The name of the stream to find.
/// The index of the stream, or -1.
public int IndexOf(string Name)
{
return base.List.IndexOf(new StreamInfo(this, Name, 0));
}
public StreamInfo this[int Index]
{
get { return (StreamInfo)base.List[Index]; }
}
public StreamInfo this[string Name]
{
get
{
int i = IndexOf(Name);
if (i==-1) return null;
else return (StreamInfo)base.List[i];
}
}
#endregion
#region Overrides
///
/// Throws an exception if you try to add anything other than a StreamInfo object to the collection.
///
protected override void OnInsert(int index, object value)
{
if (!(value is StreamInfo)) throw new InvalidCastException();
}
///
/// Throws an exception if you try to add anything other than a StreamInfo object to the collection.
///
protected override void OnSet(int index, object oldValue, object newValue)
{
if (!(newValue is StreamInfo)) throw new InvalidCastException();
}
///
/// Deletes the stream from the file when you remove it from the list.
///
protected override void OnRemoveComplete(int index, object value)
{
try
{
StreamInfo FSI = (StreamInfo)value;
if (FSI != null) FSI.Delete();
}
catch {}
}
public new StreamEnumerator GetEnumerator()
{
return new StreamEnumerator(this);
}
#endregion
#region StreamEnumerator
public class StreamEnumerator : object, IEnumerator
{
private IEnumerator baseEnumerator;
public StreamEnumerator(FileStreams mappings)
{
this.baseEnumerator = ((IEnumerable)(mappings)).GetEnumerator();
}
public StreamInfo Current
{
get
{
return ((StreamInfo)(baseEnumerator.Current));
}
}
object IEnumerator.Current
{
get
{
return baseEnumerator.Current;
}
}
public bool MoveNext()
{
return baseEnumerator.MoveNext();
}
bool IEnumerator.MoveNext()
{
return baseEnumerator.MoveNext();
}
public void Reset()
{
baseEnumerator.Reset();
}
void IEnumerator.Reset()
{
baseEnumerator.Reset();
}
}
#endregion
}
}
'@
Set-Alias block Set-DownloadFlag
Set-Alias unblock Remove-DownloadFlag
# Might want to also export: Get-Stream, Get-StreamContent, Remove-Stream, Set-StreamContent -alias block, unblock
Export-ModuleMember Get-PoshCode, New-PoshCode, Remove-DownloadFlag, Set-DownloadFlag, Get-DownloadFlag, Test-DownloadFlag, Get-WebFile, Get-PoshCodeUpgrade -alias block, unblock
# SIG # Begin signature block
# MIILCQYJKoZIhvcNAQcCoIIK+jCCCvYCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUNX6uJAfujtik0RvJGiB+u3UW
# xrqgggbgMIIG3DCCBMSgAwIBAgIJALPpqDj9wp7xMA0GCSqGSIb3DQEBBQUAMIHj
# MQswCQYDVQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxEjAQBgNVBAcTCVJvY2hl
# c3RlcjEhMB8GA1UEChMYaHR0cDovL0h1ZGRsZWRNYXNzZXMub3JnMSgwJgYDVQQL
# Ex9TY3JpcHRpbmcgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MTcwNQYDVQQDEy5odHRw
# Oi8vSHVkZGxlZE1hc3Nlcy5vcmcgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MScwJQYJ
# KoZIhvcNAQkBFhhKYXlrdWxASHVkZGxlZE1hc3Nlcy5vcmcwHhcNMDkwMzE1MTkx
# OTE5WhcNMTAwMzE1MTkxOTE5WjCBqzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5l
# dyBZb3JrMRIwEAYDVQQHEwlSb2NoZXN0ZXIxITAfBgNVBAoTGGh0dHA6Ly9IdWRk
# bGVkTWFzc2VzLm9yZzESMBAGA1UECxMJU2NyaXB0aW5nMRUwEwYDVQQDEwxKb2Vs
# IEJlbm5ldHQxJzAlBgkqhkiG9w0BCQEWGEpheWt1bEBIdWRkbGVkTWFzc2VzLm9y
# ZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPfqxOG9TQN+qZjZ6KfM
# +zBK0YpjeyPL/cFgiGBhiIdYWTBtkbZydFr3IiERKRsUJ0/SKFbhf0C3Bvd/neTJ
# qiZjH4D6xkrfdLlWMmmSXXqjSt48jZp+zfCAIaF8K84e9//7lMicdVFE6VcgoATZ
# /eMKQky4JvphJpzDHYPLxLJQrKd0pjDDwspjdX5RedWkzeZBG7VfBnebLWUzgnMX
# IxRQKfFCMryQDP8weceOnJjfJEf2FYmdpsEg5EKKKbuHsQCMVTxfteKdPvh1oh05
# 1GWyPsvEPh4auJUT8pAVvrdxq+/O9KW/UV01UxjRYM1vdklNw8g7mkJTrrHjSjl7
# tuugCnJjt5kN6v/OaUtRRMR68O85bSTVGOxJGCHUKlyuuTx9tnfIgy4siFYX1Ve8
# xwaAdN3haTon3UkWzncHOq3reCIVF0luwRZu7u+TnOAnz2BRlt+rcT0O73GN20Fx
# gyN2f5VGBbw1KuS7T8XZ0TFCspUdgwAcmTGuEVJKGhVcGAvNlLx+KPc5dba4qEfs
# VZ0MssC2rALC1z61qWuucb5psHYhuD2tw1SrztywuxihIirZD+1+yKE4LsjkM1zG
# fQwDO/DQJwkdByjfB2I64p6mk36OlZAFxVfRBpXSCzdzbgKpuPsbtjkb5lGvKjE1
# JFVls1SHLJ9q80jHz6yW7juBAgMBAAGjgcgwgcUwHQYDVR0OBBYEFO0wLZyg+qGH
# Z4WO8ucEGNIdU1T9MB8GA1UdIwQYMBaAFN2N42ZweJLF1mz0j70TMxePMcUHMAkG
# A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgTwMCoGA1UdJQEB/wQgMB4GCCsGAQUF
# BwMBBggrBgEFBQcDAgYIKwYBBQUHAwMwCwYDVR0PBAQDAgTwMCwGCWCGSAGG+EIB
# DQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQUF
# AAOCAgEAmKihxd6KYamLG0YLvs/unUTVJ+NW3jZP16R28PpmidY/kaBFOPhYyMl2
# bBGQABe7LA5rpHFAs0F56gYETNoFk0qREVvaoz9u18VfLb0Uwqtnq0P68L4c7p2q
# V3nKmWjeI6H7BAyFuogxmMH5TGDfiqrrVSuh1LtPbkV2Wtto0SAxP0Ndyts2J8Ha
# vu/2rt0Ic5AkyD+RblFPtzkCC/MLVwSNAiDSKGRPRrLaiGxntEzR59GRyf2vwhGg
# oAXUqcJ/CVeHCP6qdSTM39Ut3RmMZHXz5qY8bvLgNYL6MtcJAx+EeUhW497alzm1
# jInXdbikIh0d/peTSDyLbjS8CPFFtS6Z56TDGMf+ouTpEA16otcWIPA8Zfjq+7n7
# iBHjeuy7ONoJ2VDNgqn9B+ft8UWRwnJbyB85T83OAGf4vyhCPz3Kg8kWxY30Bhnp
# Fayc6zQKCpn5o5T0/a0BBHwAyMfr7Lhav+61GpzzG1KfAw58N2GV8KCPKNEd3Zdz
# y07aJadroVkW5R+35mSafKRJp5pz20GDRwZQllqGH1Y/UJFEiI0Bme9ecbl2vzNp
# JjHyl/jLVzNVrBI5Zwb0lCLsykApgNY0yrwEqaiqwcxq5nkXFDhDPQvbdulihSo0
# u33fJreCm2fFyGbTuvR61goSksAvLQhvijLAzcKqWKG+laOtYpAxggOTMIIDjwIB
# ATCB8TCB4zELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMRIwEAYDVQQH
# EwlSb2NoZXN0ZXIxITAfBgNVBAoTGGh0dHA6Ly9IdWRkbGVkTWFzc2VzLm9yZzEo
# MCYGA1UECxMfU2NyaXB0aW5nIENlcnRpZmljYXRlIEF1dGhvcml0eTE3MDUGA1UE
# AxMuaHR0cDovL0h1ZGRsZWRNYXNzZXMub3JnIENlcnRpZmljYXRlIEF1dGhvcml0
# eTEnMCUGCSqGSIb3DQEJARYYSmF5a3VsQEh1ZGRsZWRNYXNzZXMub3JnAgkAs+mo
# OP3CnvEwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJ
# KoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQB
# gjcCARUwIwYJKoZIhvcNAQkEMRYEFFzsqvdFDEfufk6qjB4ouF9O/ZDmMA0GCSqG
# SIb3DQEBAQUABIICAMRcKft5wE6FCP20w6c3gP9GoTu9kLVIxrSLhRgQ5hnbh7om
# smVEKzK6LZLNmByvabZAdS5ztBFamZoK3vBEbNUfVIhxG7jx1ur4Bd1ARrF4I1Nx
# JLijzSY1VCv88HNm6OKre4k0kn0d/fgDrwzvK6mIL/ZP7R8uo4cg8u85GAMLSxPN
# sMIk31cWYcrfZmqwn93YMlGUfmCos7UIMgd9Uk8dLAuyghAytfwH+KsnopaVd48D
# xERr8PCqS+7e0r8zIEFgDOpS5t+Ph4qnTReikrgs27UgUSg9AzrWSiCvrfQ7A5nf
# C2qaCz2B4/ogwbXkRewBCPxqnRmg4ZzwNl9RG41wMI3o/LKitMZBGcactlAoAUoZ
# kVvcdJ8SB0bx0BC0eGs4/BJO3am3n618con1qKj+leSs6GV33uCL6hTEEyWMmBvL
# VUO2h3Esa++PItkJfyMN1pAPADlRftx9fenxxp+GJxliR0WalG+pNODmXJrrDH5/
# uuDPn/AcjyuUoIzHig1z4d3AKGerErqDtvgU5J2rDXsCFUc9ODQXxPASoiFKrDCh
# SOsnVjcL//2tWMT11X5lwJUQ6XePwc9/lbaDr3cIKwMlLr8YdfbCuassmUk1oUdi
# iGybGZeC5GSfYj8vXyYgzv8GzNBwpvfpXyI21Nog7Z5pnnFzlq+YYK7bgcEN
# SIG # End signature block