Watch Out When Combining -Force and -WhatIf!

The -WhatIf common parameter turns on simulation mode, so a cmdlet won’t change anything and instead report what it “would have” changed. This works perfectly well, unless a developer fails to implement -WhatIf correctly.

This is rare, but there is a pattern: when you specify -Force and -WhatIf, the right thing would be to let -WhatIf win. Some developers are so focused on -Force that they let -Force win. Try it with Remove-SmbShare, for example.

Twitter This Tip! ReTweet this Tip!

Creating File Shares

In Server 2012 R2 and Windows 8.1, there are many useful new modules with cmdlets such as New-SmbShare which creates new file shares easily.

If you don’t have these cmdlets, you can often use WMI instead. That requires more investigation and googling, but once you have a code template, it works well.

To create new file shares as an Administrator, for example, try this:

$share = [wmiclass]"Win32_Share" 
$path = 'c:logs'
$name = 'LogShare'
$maxallowed = 10
$description = 'Place log files here'

$share.Create( $path, $name, 0, $maxallowed,$description,$null,$null)

Twitter This Tip! ReTweet this Tip!

Using Custom Scopes to Discard Any Output

Yesterday we looked at custom scopes to automatically restore variables and clean up behind your code.

Custom scopes can also be used to discard any output emitted by any part of code inside your scope. To do that, use this structure: $null = . { [code] }. Whatever you execute in the braces will run, and all variables and functions you might create will work outside of the scope, but there will not be any output.

Let’s look at this function:

function Out-Voice ($Text)
{
  $sapi = New-Object -ComObject Sapi.SpVoice
  $sapi.Speak($Text)
}

Out-Voice -Text 'Hello, Dude.'

When you run it, it makes Windows speak, but also outputs the number “1”. As it turns out, the method Speak() does this, and when you consider larger and more complex portions of code, there are many places where you’d produce data that you really don’t need.

Here is a fool-proof “diaper” function building that does the exact same, but is guaranteed to not return any value:

function Out-Voice ($Text)
{
  $null = . {
      $sapi = New-Object -ComObject Sapi.SpVoice
      $sapi.Speak($Text)
    }
}

Out-Voice -Text 'Hello, Dude.'

Twitter This Tip! ReTweet this Tip!

Using Custom Scopes

When you change variables, you might need to clean up later and ensure that you reverted them back to some default value – unless you use custom scopes. Yesterday, we looked at how console application errors can be handled, and when you look at the code again, you see that a lot of effort was taken to restore the $ErrorActionPreference system variable:

try
{
    # set the preference to STOP
    $old = $ErrorActionPreference
    $ErrorActionPreference = 'Stop'
    # RUN THE CONSOLE EXE THAT MIGHT EMIT AN ERROR,
    # and redirect the error channel #2 to the
    # output channel #1
    net user doesnotexist 2>&1
}

catch [System.Management.Automation.RemoteException]
{
    # catch the error emitted by the EXE,
    # and do what you want
    $errmsg = $_.Exception.Message
    Write-Warning $errmsg
}

finally
{
    # reset the erroractionpreference to what it was before
    $ErrorActionPreference = $old
}

A much easier way would be a custom scope:

& {
    try
    {
        # set the preference to STOP
        $ErrorActionPreference = 'Stop'
        # RUN THE CONSOLE EXE THAT MIGHT EMIT AN ERROR,
        # and redirect the error channel #2 to the
        # output channel #1
        net user doesnotexist 2>&1
    }

    catch [System.Management.Automation.RemoteException]
    {
        # catch the error emitted by the EXE,
        # and do what you want:
        $errmsg = $_.Exception.Message
        Write-Warning $errmsg
    }
}

The construction & { [code] } creates a new scope, and any variable defined inside of it will be deleted again once the scope exits. This is why in above example, $ErrorActionPreference is automatically restored to whatever it was before.

Twitter This Tip! ReTweet this Tip!

Catching Errors from Native EXEs

Ever wondered how you can catch errors emitted by native console EXEs? PowerShell’s error handlers can only deal with .NET code.

Here is the framework to use when you’d like to catch console application errors:

try
{
    # set the preference to STOP
    $old = $ErrorActionPreference
    $ErrorActionPreference = 'Stop'
    # RUN THE CONSOLE EXE THAT MIGHT EMIT AN ERROR,
    # and redirect the error channel #2 to the
    # output channel #1
    net user doesnotexist 2>&1
}

catch [System.Management.Automation.RemoteException]
{
    # catch the error emitted by the EXE,
    # and do what you want
    $errmsg = $_.Exception.Message
    Write-Warning $errmsg
}

finally
{
    # reset the erroractionpreference to what it was before
    $ErrorActionPreference = $old
}

Whenever the console application emits an error, it goes through console output channel #2. Since this channel is redirected to the regular output in the example above, PowerShell receives it. Whenever the ErrorActionPreference is set to “Stop”, PowerShell turns any input from that channel into a .NET RemoteException that you can catch.

 
WARNING: The user name  could not be found.
 

Twitter This Tip! ReTweet this Tip!

Advanced Error Handling: Rethrowing Exceptions

When you handle errors, you may sometimes want to replace the original exception with your own. Here is a sample:

function Do-Something
{
  # function uses internal error handling
  try
  {
    Get-Process -Name NotThereOhWell -ErrorAction Stop
  }
  # catch this error type
  catch [Microsoft.PowerShell.Commands.ProcessCommandException]
  {
    $oldE = $_.Exception

    # handle the error, OR SHOWN HERE: issue a new exception to the caller
    $newE = New-Object -TypeName System.InvalidOperationException('Do-Something: A fatal error occured', $oldE)
    Throw $newE   
  }
}

# function will encounter an internal error
# error message shows error message generated by function instead
Do-Something

This is what the caller sees:

 
PS C:>  Do-Something
Do-Something: A fatal  error occured
At line:18 char:5
+     Throw $newException
+     ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [],  InvalidOperationException
    +  FullyQualifiedErrorId : Do-Something: A fatal error occured
 

If the caller used an error handler, too, this is what would happen:

try
{
  Do-Something
}
catch [System.InvalidOperationException]
{
  [PSCustomObject]@{
    Message = $_.Exception.Message
    Originalmessage = $_.Exception.InnerException.Message
    }
}

The result would look like this:

 
Message                             Originalmessage                              
-------                             ---------------                              
Do-Something: A fatal error occured Cannot find a process with the name          
                                    "NotThereOhWell". Verify the process name    
                                    and call the cmdlet again.   
 

So the caller can see the error message returned, plus also the original error message received by Do-Something internally.

Twitter This Tip! ReTweet this Tip!