Showing posts with label Exceptions. Show all posts
Showing posts with label Exceptions. Show all posts

Sunday, July 20, 2008

The Trouble with Piping

Update:  I was wrong, and I'm really happy about it.

Powershell piping works slightly different than I thought.  As each result is returned it is passed down the pipe, rather than waiting for the entire section to complete.  My earlier tests didn't show what I thought they did, and that was my mistake.  

The function I came up with for shortcircuiting on a recursive file search when a matching file is found at the bottom of this poast, but here is another way to do the same thing based on the rest of the the thread that is much simpler.

Note:  there is one last caveat to watch out for:  if you put parentheses around some code, that code will be executed in its entirety in order to evaluate it before continuing, so keep that in mind when piping commands if you are planning on short-circuiting the process early, as in these examples.

# Find-File($path, $fileglob)
# Returns the first file that matches the fileglob.
# Args:
#   $path: [string] The path to the directory to start 
#     searching under
#   $fileglob: [string] The filename pattern to match 
#     (same format as dir)
#
# Returns:
#   The file object of the first matching file.

# New Version
function First-File([string]$path, [string]$fileglob){ 
    dir $path -include $fileglob -r | foreach {Write-Output $_; continue}
}

# Old Version
function Find-File ([string]$path, [string]$filename) { 
  $files = @(dir $path) 

  foreach ($file in $files) { 
    if (-not ($file.Mode -match '^d') -and ($file.Name -like "$filename")) { 
      return $file 
    } 
  } 

  foreach ($file in $files) { 
    if ($file.Mode -match '^d') { 
      $result = (Get-File $file.FullName $filename) 

      if ($result) { 
        return $result 
      } 
    } 
  }
}

Sunday, July 6, 2008

Importing Scripts as Libraries, Part Deux

In this post I showed how using the dotsource operator lets us run a script and import all of its named objects from the script scope into the global scope. The effect is as if instead of calling a script you just typed its contents at the prompt.

This set off a lightbulb in my head, and gave me the solution to something I'd been pondering for a while: how to import scripts as libraries (modules, etc, whatever we're calling them these days). You see, if I come up with a really neat function I don't want to have to cut and paste it into every script that uses it, and if I come up with a lot of content, I'd like to be able to share them with people.

This is what I came up with, and it incorporates a few new tricks I picked up along the way to trying to solve this problem. The desired result: I can type in 'import-script scriptname.ps1' and it is automatically imported without having to know the full path to the file.

Some highlights:
  • Test-Path is a nice cmdlet for checking the existence of a path on any PSDrive, so that means any Enviornment variable, registry key, file object, etc.
  • Exceptions work, but are really hard to get straight. Check out this link for some details.
  • The (, (command)) syntax is how I'm forcing the variable to be an array. I might get rid of it later, I think now that I'm using boolean comparison instead of looking for the length of the array to check if any files were found.
  • In most examples people use Write-Error instead of echo to print error messages, but I wanted to have the error show up clearly without all of the red words and extra garbage.
  • Note that I'm searching everything under the folder pointed to by the PSPATH environment variable. I point mine to C:\PSLibs by default.

function Import-Script ([string]$script) {
  $local:ReportErrorShowSource = 0

  # Clean up the Exception messages a bit
  trap [Exception] {
    $errmsg = "`n`tError importing '$script': "
    $errmsg += ($_.Exception.Message + "`n")
echo $errmsg
break
  }

  # Check PSPATH
  if (-not (Test-Path "Env:pspath")) {
    throw "PSPath environment variable not set."
  }elseif (-not (Test-Path $env:pspath)) {
    throw "PSPATH environment variable points to invalid path." 
  }

  $files = (, (dir $env:pspath $script -Recurse))

  # Make sure we find a single matching file
  if ($files.Length -gt 1) {
    throw ([string]$files.Length + " files of name '$script' found in PSPATH.")
  }elseif (-not $files) {
    throw "No files named '$script' found in PSPATH."
  }

  # Do the needful
  . $files[0].FullName
}