Saturday, September 20, 2014

Import-Module to Load Custom DLL

Recently a question was raised on how to find and load a custom DLL on any system.  I noted that it can be easily done using Import-Module if we place the DLL in  folder in any of the profile modules folders.

Each installation of PowerShell has a local global "Modules" folder.  To find you local modules folder, if it has been created, do the following:

Split-Path $profile

This will display the default location for the local user profile.  Now we can list the folders to see if the "Modules" folder has been created.

image

We also have a number of other profiles that we can discover like this:

image

When we use Get-Module <module name> PowerShell searches automatically through all of these folders for a Modules folder and looks in each folder for a sub-folder with a matching name.  We can place a module in any of these profile's module's folders and it will be found.

To get the path to any modules folder just split the profile path as above and add +'\Modules' to the string.

Example:

$modulepath=(Split-Path $profile.AllUsersAllHosts)+'\Modules'
dir $modulepath

image

AllUserAllHosts is a merged view of all module paths.

Now we can build and store our DLLs and script modules in any one of these paths and it can be loaded by name alone using Import-Module.

Here is the code from the Example here: Example1 or just type HELP Add-Type –online and go down to example 1.

$source=@'
public class BasicTest{

public static int Add(int a, int b){
return (a + b);
}

public int Multiply(int a, int b){
return (a * b);
}
}
'
@

Add-Type -TypeDefinition $source
[BasicTest]::Add(4, 3)
$basicTestObject = New-Object BasicTest
$basicTestObject.Multiply(5, 2)


Copy and paste this into a PowerShell prompt to see that it works as in the online example.

Now we will create a simple DLL with this code.  First we need to  open a new PowerShell CLI prompt because we have already created that type and we cannot unload it in PowerShell 4 and earlier. To unload a module we must restart PowerShell.  Now paste the following into the new PowerShell console.

$source=@'
public class BasicTest{

public static int Add(int a, int b){
return (a + b);
}

public int Multiply(int a, int b){
return (a * b);
}
}
'
@


Same source but now we are going to use it to build a DLL on disk in out Modules folder.

Start by creating the module folder.

$modules=Split-Path $profile
$BasicTest=$modules+'\BasicTest'
New-Item –Path $BasicTest –Name BasicTest –ItemType Directory

This is what you will see:

image

Now we have just one more step to create out DLL.

Add-Type -TypeDefinition $source -OutputType Library -OutputAssembly "$output\BasicTest.dll"

Now lets see what we have.

Is there a module already loaded with that name?

Get-Module BasicTest

No. Nothing is returned.  Can we find the module by name?

Get-Module –List BasicTest

image


So let's import it and test it again.

image

And that is how to both create and load your custom DLL.  In a domain environment we can distribute extensions by just saving them in the global modules folder.

Monday, September 15, 2014

Sunday, July 06, 2014

Save Command Parameters in a file.

I have not seen much about his in any blogs although all of the pieces have been blogged many times.

We may want to save the argument set for a command to use in the future however we may not want to run a different script each time we want to customize the command.  Saving a configuration and using one of many configurations is a common pattern in programming.  Saving configurations in INI, CSV and XML files is common but all have drawbacks in PowerShell.  Many time we want to have strongly types arguments like 'booleans' or script blocks and objects.  PowerShell has two file formats that can easily do this; Export-CliXML and ConvertTo-Xml

Here is a simple technique to save command parameters in a CliXml file.

We start with a simple command function that was borrowed from another project because it can be altered to demonstrate how this technique works.  Any CmdLet or advanced function can use this technique

We will start with this part of a function that has numerous parameters that we might want to apply from a file.

The Test function

function New-FileSystemWatcher{
Param(
[string]$Name='FSW',
[Parameter(Mandatory=$true)]
[string]$Path,
[bool]$IncludeSubdirectories=$false,
[string]$Filter='*.*',
[Parameter()]
[ValidateSet(
'FileName','DirectoryName','Attributes','Size',
'LastWrite','LastAccess','CreationTime','Security'
)][string[]]$NotifyFilter,
[int]$BufferSize=8192,
[scriptblock]$ChangedScript,
[scriptblock]$CreatedScript,
[scriptblock]$RenamedScript,
[scriptblock]$DeletedScript,
[scriptblock]$ErrorScript,
[switch]$AllOnChangedScript
)
}

This function has a pretty good range of parameter types and requirements.  It should help with testing.

The Splat

We can call our function with full or partial arguments like this:

New-FileSystemWatcher –Name MyTest  -Path c:\somefolder –Filter *.txt … etc

If we add one line to out test function we can see the results of our call arguments.

$PSBoundParameters

We can pass arguments and get a list of the arguments that we have passed ('bound') like this:

image

If we pass more arguments we will get them displayed.

image

Now we can splat and test.

What is a "splat"?

Here is a very simple explanation of a splat: http://technet.microsoft.com/en-us/magazine/gg675931.aspx

In simple terms a "splat" is shorthand for bundled arguments.    A splat is a hash containing our arguments:

$props=@{Name='SplatTest';Path='e:\somepath'}

image

If we take the variable named 'props' and add '@' in front we have told PowerShell to use the hash to supply the parameter arguments by name.  You can see that the output echoes the exact arguments we have supplied in the 'Splat' or @spalt.

How can we use a @Splat from a file?

Start by creating a template from the 'Param' statement and edit it to be a legal hash table  I did this here by removing all decoration and removing the $' from all parameters like this:

$param=@{
Name=[string]'Default'
Path=[string]$pwd
IncludeSubdirectories=$false
Filter='*.*'
NotifyFilter=[string[]]'LastWrite'
BufferSize=[int]8192
ChangedScript=$null
CreatedScript=[scriptblock]$null
RenamedScript=[scriptblock]$null
DeletedScript=[scriptblock]$null
ErrorScript=[scriptblock]$null
AllOnChangedScript=[bool]$null
}

.You can set any Name to an default value you find is useful or required for your template. Here I supplied some required defaults lie Name, Path and NotifyFilter.,

This is the result of test my splat template:

image

Now we save the template to an CliXML file like this:

$param | Export-CliXml FSWTemplate.clixml

We can import it and alter any parameters needed to create a custom argument set.

image
.

Now we can load a template and create custom copies by just exporting the changed hash table to a new file.  We can even create a file loaded with hash tables that can be added to and extracted by name.  We can do all of this without using XML or managing strings or custom file structures.

To find out how to build and edit files with multiple hash tables see the next blog post.

Monday, May 12, 2014

Extracting Strings from Event Records #1

With event records in the old formmat obtained by using PowerShell Get-EventLog we can extract the strings as an array.

Example #1: 

PS C:\scripts> Get-EventLog -LogName Security -InstanceId 4634 -Newest 1|
>>Select -expand ReplacementStrings
S-1-5-7
ANONYMOUS LOGON
NT AUTHORITY
0xac30fba
3

Notice there are 5 strings.  They are an array.  We can use them in a formatted query like this:

Example #2: 

PS C:\scripts> Get-EventLog -LogName Security -InstanceId 4634 -Newest 1|
>> Select @{N='AccountSID';E={$_.ReplacementStrings[0]}}
>>

AccountSID
----------
S-1-5-21-2768830276-2144858717-3390379511-1000

Or more:

Example #3: 

PS C:\scripts> Get-EventLog -LogName Security -InstanceId 4634 -Newest 1|
>>    Select @{N='AccountSID';E={$_.ReplacementStrings[0]}},
>>           @{N='Domain';E={$_.ReplacementStrings[1]}}
>>

AccountSID                                                  Domain
----------                                                  ------
S-1-5-7                                                     ANONYMOUS LOGON

It is really that simple.