Compressed Function Libraries

The problem with Powershell scripts is that you can’t run them while they are compressed. You may not be able to run them while they are compressed, but I can. I have written two scripts that can be used to import and export functions, and the exported functions can be compressed.
The export function will read named functions out of the current runspace and save them as xml to a file. Those exported functions can optionally be compressed with GZip. The import function can then read the file and parse the xml to get to the functions. The imported functions can then be written back into the runspace or written to the output buffer.
The GZip functionality uses the GZipStream that comes with the .NET Framework API so there no need to employ an external command for compression. GZip is also a very fast compression format and saving xml function libraries as GZip compressed will not cause significant overhead.
There are two noteworthy limitations on the scripts. The first and more significant limitation is that the libraries are not yet checked for signatures. This means that if the import script can run, then any function stored in an xml function library can be run. The other limitation is that only GZip is supported. With modifications many different compression formats could be added, however, and I do intend to eventually add support for LZMA, the compression format that is used by 7Zip.
Adding signature checking is my first priority for revising these scripts, and there is not a great amount of documentation on how that could be accomplished, so any comments on how to do so would be appreciated. Any general comments would also be appreciated. 
 
# Export-FunctionLibrary.ps1
# Version: 1.0
# Author: LunaticExperimentalist
# End User License: Public Domain

param ([String]$Path, [String[]]$FunctionNames,
	[String]$Encoding = 'utf8', [String]$CompressionFormat = 'None')

if (($args -contains "-?") -or (-not $Path) -or (-not $FunctionNames)) {
	"Export-FunctionLibrary"
	"VERSION: 1.0"
	"AUTHOR: LunaticExperimentalist"
	""
	"USAGE: Export-FunctionLibrary -Path [String] -FunctionNames [String[]]"
	"       [-Encoding [String]] [-CompressionFormat [String]]"
	""
	"PARAMETERS:"
	"  Path: A manditory parameter containing a path string to a file where the"
	"        selected functions are to be stored."
	""
	"  FunctionNames: A manditory parameter containing a list of function names."
	"                 The use of path style wildcards may be used here."
	""
	"  Encoding: An optional parameter selecting an encoding to be used when"
	"            exporting the xml text file. Valid encodings are utf8, utf16le,"
	"            and utf16be."
	""
	"  CompressionFormat: An optional parameter selecting a compression format to"
	"                     be applyed to the resulting library. Only GZip is"
	"                     supported by this version."
	""
	"DESCRIPTION: This script copys the code from one or more functions and saves"
	"             them to an xml formated file. The resuling xml can optionally"
	"             be compressed."
	""
	"SEE ALSO: Import-FunctionLibrary"
	return
}

if (('none','gzip') -notcontains $CompressionFormat) {
	throw 'Invalid compression format. Available formats are None and GZip.'
}

if (('utf8','utf16le','utf16be') -notcontains $Encoding) {
	throw 'Invalid encoding. Available encodings are utf8, utf16le, and utf16be.'
}

function IsFunctionNamed {
	param ([String]$FunctionName)
	$FunctionNames | Foreach { if ($FunctionName -like $_) { return $True } }
	return $False
}

function XMLEncode {
	param ([String]$Text)
	return $Text -replace '&','&' -replace '<','<' -replace '>','>'
}

# Generate the xml containg the names and script form the functions indicated by $FunctionNames
$LibraryXML = [String]::Concat($(
	''
	Get-ChildItem Function: | Foreach { if (IsFunctionNamed($_.Name)) {
		"$(XMLEncode($_.Name))$(XMLEncode($_.ScriptBlock.ToString()))"
	} }
	""
))

# Encode the xml
$Unicode = $( switch ($Encoding) {
	utf8 {New-Object System.Text.UTF8Encoding($False); break}
	utf16le {New-Object System.Text.UnicodeEncoding($False,$True); break}
	utf16be {New-Object System.Text.UnicodeEncoding($True,$True); break}
} )

[Byte[]]$LibraryBytes = @($Unicode.GetPreamble(); $Unicode.GetBytes($LibraryXML))

# Open a writable file at the path provided
[IO.FileStream]$FileStream = $Null
if (Test-Path $Path) {
	# File exists
	# Overwrive old file
	$FileStream = (Get-Item -Path $Path).Create()
}
else {
	# File does not exist
	# Create new file
	$FileStream = (New-Item -Path $Path -Type File).Create()
}

# Write the library byte data to the file
if ($CompressionFormat -eq 'gzip') {
	# GZip was requested
	$GZip = New-Object System.IO.Compression.GZipStream($FileStream, 'Compress')
	$GZip.Write($LibraryBytes, 0, $LibraryBytes.Length)
	$GZip.Close()
}
else {$FileStream.Write($LibraryBytes, 0, $LibraryBytes.Length); $FileStream.Close()}
# Import-FunctionLibrary.ps1
# Version: 1.0
# Author: LunaticExperimentalist
# End User License: Public Domain

param ([String]$Path, [String[]]$FunctionNames = '*',
	[String]$CompressionFormat = 'None', [Switch]$List)

if (($args -contains "-?") -or (-not $Path)) {
	"Import-FunctionLibrary"
	"VERSION: 1.0"
	"AUTHOR: LunaticExperimentalist"
	""
	"USAGE: Import-FunctionLibrary -Path [String] -FunctionNames [String[]]"
	"       [-CompressionFormat [String]]"
	""
	"PARAMETERS:"
	"  Path: A manditory parameter containing a path string to a file where the"
	"        selected functions are stored."
	""
	"  FunctionNames: An optional parameter containing a list of function names."
	"                 The use of path style wildcards may be used here."
	""
	"  CompressionFormat: An optional parameter selecting a compression format to"
	"                     be applyed to the resulting library. Only GZip is"
	"                     supported by this version. The default value is none."
	""
	"  List: A swtch parameter that causes Import-FunctionLibrary to output the"
	"        content of the library without creating new functions in the current"
	"        runspace."
	""
	"DESCRIPTION: This script reads functions form a file and makes them available"
	"             to the command line."
	""
	"SEE ALSO: Export-FunctionLibrary"
	return
}

if (('none','gzip') -notcontains $CompressionFormat) {
	throw 'Invalid compression format. Available formats are None and GZip.'
}

function IsFunctionNamed {
	param ([String]$FunctionName)
	$FunctionNames | Foreach { if ($FunctionName -like $_) { return $True } }
	return $False
}

# Read text form file
if (-not (Test-Path $Path)) {throw "Path not found."}

$LibraryText=$Null
if ($CompressionFormat -eq 'gzip') {
	# Read GZiped content
	$FileStream = (Get-Item $Path).OpenRead()
	$GZipStream = New-Object System.IO.Compression.GZipStream($FileStream,'Decompress')
	$FileReader = New-Object System.IO.StreamReader($GZipStream, $True)
	$LibraryText = $FileReader.ReadToEnd()
	$FileReader.Close()
}
else { $LibraryText = Get-Content $Path }

# Parse text as xml
$(
	trap {throw "The library text could not be parsed as xml."}
	$LibraryXML = [xml]$LibraryText
)

# Filter and apply functions from xml to the current runspace
$LibraryXML.SelectNodes('/library/function') | Foreach {
	if (IsFunctionNamed($_.Name)) {
		if ($List) {
			# Write functions to output
			$Container = New-Object PSObject
			Add-Member -MemberType NoteProperty -Name Name -Value ($_.Name) -InputObject $Container
			Add-Member -MemberType NoteProperty -Name Script -Value ($_.ScriptBlock) -InputObject $Container
			$Container
		}
		else {
			# Make functions available for use
			$FunctionName = $_.Name
			$(
				trap {Write-Error ('Could not import function "'+$FunctionName+'".'); continue}
				Set-Content -LiteralPath ("Function:"+$FunctionName) -Value $_.ScriptBlock
			)
		}
	}
}

Advertisements

~ by lunaticexperimentalist on September 2, 2007.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: