May 2008 - Posts
Heres a useful snippit of code I wrote as a utility and thought I'd post up for everyone. Very often when using a command line, you need to repeat a set of commands. For example, copy several files:
copy "c:\blah.txt" "c:\otherPlace\blah.txt"
copy "c:\dev\blah.txt" "c:\otherPlace\blah2.txt"
History is okay for this, but if you have a set of commands you need to run, you need to run them each individually. Also, if you go to do something else, they'll eventually lose their spot in the command history.
To solve this, I created a "scratch pad". Nothing special, but definately useful.
new-scratch "MyFirstScratch"
copy "c:\blah.txt" "c:\otherPlace\blah.txt"
scratch
copy "c:\dev\blah.txt" "c:\otherplace\blah2.txt"
scratch
## Do some other stuff here...
get-scratch # prints out the contents of the scratch.
invoke-scratch
This scratch pad is holds a set of commands that you want to rerun later. You can then call invoke-scratch to run the scratchpad. You can have multiple scratch pads, and use them by just adding the name to the end of the function (get-scratch "MyFirstScratch"). By default all scratch pad commands will default to the last used scratch pad.
The example below shows how to shorten the sample above:
new-scratch "MyFirstScratch"
copy "c:\blah.txt" "c:\otherPlace\blah.txt"
copy "c:\dev\blah.txt" "c:\otherplace\blah2.txt"
scratch 2 # Saves the last 2 items to the scratch pad
This can be very useful for writing scripts too. the Save-Scratch command lets you export your scratch to a file.
Heres the code. Add it to your profile and go. Enjoy!
function New-Scratch($name) {
if ($global:scratchPads -eq $null) {
$global:scratchPads = @{}
}
$pad = new-object System.Collections.ArrayList
$global:scratchPads.Add($name, $pad)
$global:currentScratchPad = $pad
$global:currentScratchPadName = $name
}
function Get-Scratch($name=$null) {
if ($name -ne $null) {
$global:currentScratchPad = $global:scratchPads[$name]
$global:currentScratchPadName = $name
}
return $global:currentScratchPad
}
function Scratch([int]$count=1,$name=$null) {
if ($name -ne $null) {
$global:currentScratchPad = $global:scratchPads[$name]
$global:currentScratchPadName = $name
}
$hist = get-history
if ($name -eq $null) {
$name = $global:currentScratchPadName
}
for ($i=$hist.Length-$count; $i -lt $hist.Length; $i++) {
$global:currentScratchPad.Add($hist[$i].CommandLine) | out-null
}
}
function Invoke-Scratch($name=$null) {
if ($name -ne $null) {
$global:currentScratchPad = $global:scratchPads[$name]
$global:currentScratchPadName = $name
}
foreach ($item in $global:currentScratchPad) {
write-host ">> $item"
invoke-expression $item
}
}
function Save-Scratch($path, $name=$null) {
if ($name -ne $null) {
$global:currentScratchPad = $global:scratchPads[$name]
$global:currentScratchPadName = $name
}
$global:currentScratchPad | out-file $path
}
If you want to run your PowerShell scripts from a piece of code you're writing, and would prefer not to use Process.Start, you can easily use the PowerShell runtime classes to run those scripts for you, completely in-process.
The code snippit below shows how you can create a Runspace and pass it some simple commands.
List<Command> commands = new List<Command>();
commands.Add(new Command("set-location c:\\"));
commands.Add(new Command("./MyScript.ps1 'myParameter'", true));
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
using (Pipeline pipeline = runspace.CreatePipeline())
{
foreach (Command cmd in commands)
{
pipeline.Commands.Add(cmd);
}
pipeline.Invoke();
}
runspace.Close();
}
One thing to note here is you do not have a PSHost, so if you have a write-host anywhere, it will fail with the following exception:
CmdletInvocationException: Cannot invoke this function becasue the current host does not implement it.
To get around this, you can remove write-host from your scripts and just have them write single lines, or you can implement a simple PSHost (although this is a bit harder than it sounds).
If you've been using System.DirectoryServices.DirectoryEntry class, or the newer System.DirectoryServices.AccountManagement namespace to access your LDAP or Active Directory server, you may have experienced the following error:
COMException: "Unknown error (0x80005000)"
This can happen for numerous reasons, but one of the most frustrating and overlooked reason's I've found for this problem is when your LDAP connection string is malformed. One of the most common malformations is in the case sensitivity of the LDAP:// component. For example, LDAP://myServer/cn=users,dc=myserver,dc=com is a valid connection string, however ldap://myServer/cn=users,dc=myserver,dc=com is not.
If you use the Uri or UriBuilder classes, the builder may lowecase your scheme. Always make sure to recapitalize the scheme when passing it into DirectoryEntry or any other API.
More Posts