PowerShell Quick Tip: Creating wide tables with PowerShell

A few weeks back I took a trip to Columbus, Ohio where I had the opportunity to meet with the members of the Central Ohio PowerShell Users Group and talk about PowerShell and PowerGUI®.  During that event, one of the attendees highlighted a problem they were having.  The problem he highlighted is a fairly common one, so I wanted to share the problem and solution here.

When using PowerShell to generate tabular output, the table that is generated is limited by the width of the buffer.  This is intentional so that the tables always fit in your console window so that they can be easily understood, and for the most part this doesn’t get in the way too much because you can control which columns are shown in the table so that you get the data that is most important to you or you can use Format-List to generate list output when you really want a lot of data.  It can get in the way though if you want to share data from a large table with someone else, store data in a text file for archival purposes, etc.  In these cases you are faced with a challenge because Format-Table does not appear to allow you to create large tables like this due to a few issues that get in your way.

Issue #1: PowerShell truncates table output by default

Let’s say you want to create a table showing a list of aliases for Invoke commands and you want to view all properties for those aliases.  The command to do this seems pretty straightforward, simply calling Get-Alias -Definition Invoke-* and passing the results to Format-Table -Property * to show all of the columns.  Here are the results of that command, showing a table with column names that span multiple lines and rows with data that is not useful:

image

As you can see, the content of the table is truncated so you can’t really tell what the data is in the table, and if you output the results of that command to a file where the lines can be longer you still get the same results.

Issue #2: Auto-sized tables are limited to the width of your screen buffer

If you try to solve this problem by looking at the help for Format-Table, you may come across the AutoSize parameter.  The AutoSize parameter allows you to tell PowerShell that you want the formatter to automatically size the table columns, showing as much data as possible.  Here is what happens if you add the AutoSize parameter to the same command you just ran:

image

This is on the right track for what you are after, but notice the warning text that is output at the top of the resulting table.  It indicates that 10 columns do not fit into the display and were removed.  If you pipe the results of this command to Out-File, PowerShell will simply pass what you see on the screen to the file you are writing to, which is not sufficient for your needs.

Solution: Strings are not subject to the limitations of other objects in PowerShell

When you pass the results of Get-Alias to Format-Table, the Alias data is converted into objects that describe how the table should look.  These objects are then passed implicitly to the Out-Default cmdlet which renders the objects according to their format configuration as well as the current console configuration.  In fact, every PowerShell command you run ends with the results being implicitly passed to Out-Default.  Even if you pass the results of our Format-Table call to Out-File instead, Out-File will render the objects according to their format configuration and the current console configuration.  This is the limitation that is making this seemingly simple task so difficult.  What we need to do then is to make sure that the data that is sent to Out-File or Out-Default is pre-formatted so that it is not limited by the size of the screen buffer.  How do you do that you ask?  The answer is simple: use Out-String.

Out-String is a cmdlet that allows you to convert any output to string format, and you can specify the width of that string using the Width parameter which allows you to exceed the limitations of the PowerShell console buffer size.  When strings are passed into Out-Default and Out-File they are simply written as is without any truncation or automatic text wrapping.  With that additional piece of knowledge you can pass the results of your Format-Table cmdlet call to Out-String using a width that is large enough for the table output, which successfully forces the objects to be rendered with the width that you want without having to change the buffer size.

Here is a screenshot showing what happens when you pass your Format-Table output to Out-String, specifying a width of 4096 characters:

image

That might not look very useful either, but look what happens when you take this one step further and pass the results to Out-File.  Here is the command to do this:

Get-Alias -Definition Invoke-* `
| Format-Table -Property * -AutoSize `
| Out-String -Width 4096 `
| Out-File C:\aliases.txt

Opening the resulting aliases.txt file shows the following contents:

image

This is only showing part of the results, but did you notice the horizontal scroll bar at the bottom of the window?  It illustrates that you have achieved your goal, creating a table wide enough to show all data in the table inside of a text file that can then be sent to others, stored for archival logging purposes, etc.

Sidebar: If you have a specific property that contains a collection of items, that property may still show an ellipsis in the file produced here if the number of items in that collection exceeds the number assigned to the built-in $FormatEnumerationLimit variable.  In this case, if you want to see the entire collection of items, you should temporarily change the value in $FormatEnumerationLimit to something large enough to show your collection (-1 if you want to see all items) and then run a command similar to what I have shown you above.  The default value of $FormatEnumerationLimit is 4, which can be limiting when generating a file like this.  Increasing this value will give you a full report of what you have in the data you are processing.

With that knowledge in hand, the takeaway for you here is to make sure that you autosize your Format-Table results and that you pass them to Out-String with an appropriate width before you write it to a file if you want to output large tables in text format.

Thanks for listening!

Kirk out.

PowerShell Quick Tip: Process your errors more efficiently with Group-Object

Instead of going through your errors one at a time, since there are often cases where the same error is repeated multiple times, use Group-Object with $error to filter out the duplicates so that you can see what the real errors are that you need to deal with.  For example, right now I have 256 errors stored in my $error variable.  To figure out what I need to really look at, I can invoke this command:When working with large script files or modules, you may encounter situations where you have a large number of errors that you need to deal with.  This can be overwhelming, especially when you have looping constructs in your scripts that cause the errors to be raised over and over again.  When you are facing hundreds of errors output from a script, how do you process them all?  Fortunately there is a quick an easy way to make sense of your errors and identify what really needs to be fixed.

All errors are recorded by default in the $error variable.  This variable allows you to access any errors you have received in PowerShell, to a maximum count as identified by the $MaximumErrorCount variable.  By default this is set to 256, and dealing with 256 errors can be impossible without the proper divide and conquer strategy.  Also, the $error variable even includes errors that were hidden by trap, catch, or error action preferences, so this variable is very useful when troubleshooting all errors you have in your scripts.  As mentioned though, this can be a lot of information to digest, so it is important to know how to process it efficiently.

Instead of going through your errors one at a time, since there are often cases where the same error is repeated multiple times, use Group-Object with $error to filter out the duplicates so that you can see what the real errors are that you need to deal with.  For example, right now I have 256 errors stored in my $error variable.  To figure out what I need to really look at, I can invoke this command:

$error | Group-Object | Format-Table Count,Name -AutoSize

That gives me the following results:

Count Name
----- ----
1 Exception calling "ShowDialog" with "0" argument(s): "Property 'PS...
2 Property 'PSiscontainer' cannot be found on this object. Make sure...
240 Property 'Extension' cannot be found on this object. Make sure tha...
13 Property 'Name' cannot be found on this object. Make sure that it ...

This short list of four errors is much less intimidating than a huge list of 250 errors.  Once you have the short list, it becomes much easier to figure out where to get started.  You can also go further by expanding on these details like this:

$Error | Group-Object | Format-List Count,@{Name='Error';Expression={$_.Name}},@{Name='Location';Expression={$_.Group[0].InvocationInfo.PositionMessage}}

A command like that will yield results that look something like the following:

Count    : 1
Error    : Exception calling "ShowDialog" with "0" argument(s): "Property '
PSiscontainer' cannot be found on this object. Make sure that it
exists."
Location :
At C:\Users\kmunro\Documents\WindowsPowerShell\Modules\Add-on.Te
st\Add-on.Test.psm1:860 char:20
+         $Form1.ShowDialog <<<< ()
Count    : 2
Error    : Property 'PSiscontainer' cannot be found on this object. Make su
re that it exists.
Location :
At C:\Users\kmunro\Documents\WindowsPowerShell\Modules\Add-on.Te
st\Add-on.Test.psm1:1236 char:16
+                         if ($obj. <<<< PSiscontainer) {
Count    : 240
Error    : Property 'Extension' cannot be found on this object. Make sure t
hat it exists.
Location :
At line:1 char:11
+ if ($this. <<<< Extension.Length -gt 0){$this.Name.Remove($thi
s.Name.Length - $this.Extension.Length)}else{$this.Name}
Count    : 13
Error    : Property 'Name' cannot be found on this object. Make sure that i
t exists.
Location :
At line:1 char:7
+ $this. <<<< Name

That’s much better.  Now I can see which errors are happening most frequently and concentrate my efforts on them while knowing I only have a few unique errors to deal with.

I hope this helps you work out errors in your own scripts and modules.

Kirk out.

P.S. Wouldn’t it be cool if there was a PowerGUI Script Editor Add-on that showed you the contents of the $error variable, grouped like this, with the ability to double-click and go to the appropriate position in the appropriate file…could make a great entry for the PowerGUI Challenge contest that is going on right now!

PowerShell Quick Tip: Getting all of the members of an object

If you weren’t already aware, using the Get-Member cmdlet is essential to understanding what you can do with PowerShell.  Get-Member allows you to identify the members (properties, methods, events, etc.) that are available on the objects you are working with.  What some people don’t realize with Get-Member however is that by default it does not identify all members that are available on the objects that are passed into it.

For example, let’s take a look at how you might use Get-Member to see what you can do with Win32_BIOS objects, like this:

Get-WmiObject Win32_BIOS | Get-Member

That command yields the following results:

   TypeName: System.Management.ManagementObject#root\cimv2\Win32_BIOS

Name                  MemberType   Definition
—-                  ———-   ———-
BiosCharacteristics   Property     System.UInt16[] BiosCharacteristics {get;set;}
BIOSVersion           Property     System.String[] BIOSVersion {get;set;}
BuildNumber           Property     System.String BuildNumber {get;set;}
Caption               Property     System.String Caption {get;set;}
CodeSet               Property     System.String CodeSet {get;set;}
CurrentLanguage       Property     System.String CurrentLanguage {get;set;}
Description           Property     System.String Description {get;set;}
IdentificationCode    Property     System.String IdentificationCode {get;set;}
InstallableLanguages  Property     System.UInt16 InstallableLanguages {get;set;}
InstallDate           Property     System.String InstallDate {get;set;}
LanguageEdition       Property     System.String LanguageEdition {get;set;}
ListOfLanguages       Property     System.String[] ListOfLanguages {get;set;}
Manufacturer          Property     System.String Manufacturer {get;set;}
Name                  Property     System.String Name {get;set;}
OtherTargetOS         Property     System.String OtherTargetOS {get;set;}
PrimaryBIOS           Property     System.Boolean PrimaryBIOS {get;set;}
ReleaseDate           Property     System.String ReleaseDate {get;set;}
SerialNumber          Property     System.String SerialNumber {get;set;}
SMBIOSBIOSVersion     Property     System.String SMBIOSBIOSVersion {get;set;}
SMBIOSMajorVersion    Property     System.UInt16 SMBIOSMajorVersion {get;set;}
SMBIOSMinorVersion    Property     System.UInt16 SMBIOSMinorVersion {get;set;}
SMBIOSPresent         Property     System.Boolean SMBIOSPresent {get;set;}
SoftwareElementID     Property     System.String SoftwareElementID {get;set;}
SoftwareElementState  Property     System.UInt16 SoftwareElementState {get;set;}
Status                Property     System.String Status {get;set;}
TargetOperatingSystem Property     System.UInt16 TargetOperatingSystem {get;set;}
Version               Property     System.String Version {get;set;}
__CLASS               Property     System.String __CLASS {get;set;}
__DERIVATION          Property     System.String[] __DERIVATION {get;set;}
__DYNASTY             Property     System.String __DYNASTY {get;set;}
__GENUS               Property     System.Int32 __GENUS {get;set;}
__NAMESPACE           Property     System.String __NAMESPACE {get;set;}
__PATH                Property     System.String __PATH {get;set;}
__PROPERTY_COUNT      Property     System.Int32 __PROPERTY_COUNT {get;set;}
__RELPATH             Property     System.String __RELPATH {get;set;}
__SERVER              Property     System.String __SERVER {get;set;}
__SUPERCLASS          Property     System.String __SUPERCLASS {get;set;}
PSStatus              PropertySet  PSStatus {Status, Name, Caption, SMBIOSPresent}
ConvertFromDateTime   ScriptMethod System.Object ConvertFromDateTime();
ConvertToDateTime     ScriptMethod System.Object ConvertToDateTime();

That’s a pretty good list of 40 members, but there are actually many more that you might like to see as well.  For example, it can be useful to see the different member sets available on an object as well as any get_ and set_ methods that object has.  You can see that information by using the –Force parameter with Get-Member.  It can also be useful to see any hidden members that an object has, whether they come from the base member set, extended member set, or adapted member set.  To see all of these hidden members, you can use the –View parameter and specify you want to see members from All views.  When you put the –Force and –View parameters together, you end up with the following command:

Get-WmiObject Win32_BIOS | Get-Member -Force -View All

Running that command yields the following results:

   TypeName: System.Management.ManagementObject#root\cimv2\Win32_BIOS

Name                      MemberType            Definition
—-                      ———-            ———-
pstypenames               CodeProperty          System.Collections.ObjectModel.Collection`1[[System.String, mscorlib…
Disposed                  Event                 System.EventHandler Disposed(System.Object, System.EventArgs)
psadapted                 MemberSet             psadapted {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY, __RELPATH, __P…
psbase                    MemberSet             psbase {Scope, Path, Options, ClassPath, Properties, SystemPropertie…
psextended                MemberSet             psextended {ConvertToDateTime, ConvertFromDateTime, PSStatus}
psobject                  MemberSet             psobject {Members, Properties, Methods, ImmediateBaseObject, BaseObj…
PSStandardMembers         MemberSet             PSStandardMembers {DefaultDisplayPropertySet}
add_Disposed              Method                System.Void add_Disposed(System.EventHandler value)
Clone                     Method                System.Object Clone()
CompareTo                 Method                bool CompareTo(System.Management.ManagementBaseObject otherObject, S…
CopyTo                    Method                System.Management.ManagementPath CopyTo(System.Management.Management…
CreateObjRef              Method                System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
Delete                    Method                System.Void Delete(), System.Void Delete(System.Management.DeleteOpt…
Dispose                   Method                System.Void Dispose()
Equals                    Method                bool Equals(System.Object obj)
Get                       Method                System.Void Get(), System.Void Get(System.Management.ManagementOpera…
GetHashCode               Method                int GetHashCode()
GetLifetimeService        Method                System.Object GetLifetimeService()
GetMethodParameters       Method                System.Management.ManagementBaseObject GetMethodParameters(string me…
GetPropertyQualifierValue Method                System.Object GetPropertyQualifierValue(string propertyName, string …
GetPropertyValue          Method                System.Object GetPropertyValue(string propertyName)
GetQualifierValue         Method                System.Object GetQualifierValue(string qualifierName)
GetRelated                Method                System.Management.ManagementObjectCollection GetRelated(), System.Ma…
GetRelationships          Method                System.Management.ManagementObjectCollection GetRelationships(), Sys…
GetText                   Method                string GetText(System.Management.TextFormat format)
GetType                   Method                type GetType()
get_ClassPath             Method                System.Management.ManagementPath get_ClassPath()
get_Container             Method                System.ComponentModel.IContainer get_Container()
get_Item                  Method                System.Object get_Item(string propertyName)
get_Options               Method                System.Management.ObjectGetOptions get_Options()
get_Path                  Method                System.Management.ManagementPath get_Path()
get_Properties            Method                System.Management.PropertyDataCollection get_Properties()
get_Qualifiers            Method                System.Management.QualifierDataCollection get_Qualifiers()
get_Scope                 Method                System.Management.ManagementScope get_Scope()
get_Site                  Method                System.ComponentModel.ISite get_Site()
get_SystemProperties      Method                System.Management.PropertyDataCollection get_SystemProperties()
InitializeLifetimeService Method                System.Object InitializeLifetimeService()
InvokeMethod              Method                System.Object InvokeMethod(string methodName, System.Object[] args),…
Put                       Method                System.Management.ManagementPath Put(), System.Management.Management…
remove_Disposed           Method                System.Void remove_Disposed(System.EventHandler value)
SetPropertyQualifierValue Method                System.Void SetPropertyQualifierValue(string propertyName, string qu…
SetPropertyValue          Method                System.Void SetPropertyValue(string propertyName, System.Object prop…
SetQualifierValue         Method                System.Void SetQualifierValue(string qualifierName, System.Object qu…
set_Item                  Method                System.Void set_Item(string propertyName, System.Object value)
set_Options               Method                System.Void set_Options(System.Management.ObjectGetOptions value)
set_Path                  Method                System.Void set_Path(System.Management.ManagementPath value)
set_Scope                 Method                System.Void set_Scope(System.Management.ManagementScope value)
set_Site                  Method                System.Void set_Site(System.ComponentModel.ISite value)
ToString                  Method                string ToString()
Item                      ParameterizedProperty System.Object Item(string propertyName) {get;set;}
BiosCharacteristics       Property              System.UInt16[] BiosCharacteristics {get;set;}
BIOSVersion               Property              System.String[] BIOSVersion {get;set;}
BuildNumber               Property              System.String BuildNumber {get;set;}
Caption                   Property              System.String Caption {get;set;}
ClassPath                 Property              System.Management.ManagementPath ClassPath {get;}
CodeSet                   Property              System.String CodeSet {get;set;}
Container                 Property              System.ComponentModel.IContainer Container {get;}
CurrentLanguage           Property              System.String CurrentLanguage {get;set;}
Description               Property              System.String Description {get;set;}
IdentificationCode        Property              System.String IdentificationCode {get;set;}
InstallableLanguages      Property              System.UInt16 InstallableLanguages {get;set;}
InstallDate               Property              System.String InstallDate {get;set;}
LanguageEdition           Property              System.String LanguageEdition {get;set;}
ListOfLanguages           Property              System.String[] ListOfLanguages {get;set;}
Manufacturer              Property              System.String Manufacturer {get;set;}
Name                      Property              System.String Name {get;set;}
Options                   Property              System.Management.ObjectGetOptions Options {get;set;}
OtherTargetOS             Property              System.String OtherTargetOS {get;set;}
Path                      Property              System.Management.ManagementPath Path {get;set;}
PrimaryBIOS               Property              System.Boolean PrimaryBIOS {get;set;}
Properties                Property              System.Management.PropertyDataCollection Properties {get;}
Qualifiers                Property              System.Management.QualifierDataCollection Qualifiers {get;}
ReleaseDate               Property              System.String ReleaseDate {get;set;}
Scope                     Property              System.Management.ManagementScope Scope {get;set;}
SerialNumber              Property              System.String SerialNumber {get;set;}
Site                      Property              System.ComponentModel.ISite Site {get;set;}
SMBIOSBIOSVersion         Property              System.String SMBIOSBIOSVersion {get;set;}
SMBIOSMajorVersion        Property              System.UInt16 SMBIOSMajorVersion {get;set;}
SMBIOSMinorVersion        Property              System.UInt16 SMBIOSMinorVersion {get;set;}
SMBIOSPresent             Property              System.Boolean SMBIOSPresent {get;set;}
SoftwareElementID         Property              System.String SoftwareElementID {get;set;}
SoftwareElementState      Property              System.UInt16 SoftwareElementState {get;set;}
Status                    Property              System.String Status {get;set;}
SystemProperties          Property              System.Management.PropertyDataCollection SystemProperties {get;}
TargetOperatingSystem     Property              System.UInt16 TargetOperatingSystem {get;set;}
Version                   Property              System.String Version {get;set;}
__CLASS                   Property              System.String __CLASS {get;set;}
__DERIVATION              Property              System.String[] __DERIVATION {get;set;}
__DYNASTY                 Property              System.String __DYNASTY {get;set;}
__GENUS                   Property              System.Int32 __GENUS {get;set;}
__NAMESPACE               Property              System.String __NAMESPACE {get;set;}
__PATH                    Property              System.String __PATH {get;set;}
__PROPERTY_COUNT          Property              System.Int32 __PROPERTY_COUNT {get;set;}
__RELPATH                 Property              System.String __RELPATH {get;set;}
__SERVER                  Property              System.String __SERVER {get;set;}
__SUPERCLASS              Property              System.String __SUPERCLASS {get;set;}
PSStatus                  PropertySet           PSStatus {Status, Name, Caption, SMBIOSPresent}
ConvertFromDateTime       ScriptMethod          System.Object ConvertFromDateTime();
ConvertToDateTime         ScriptMethod          System.Object ConvertToDateTime();

That result set shows 99 members, which is a lot more than the 40 members you get to see by default!

As you can see, there is a lot more to these objects than meets the eye.  Using Get-Member with –Force and –View All is a great way to get all of the information you need about objects so that you really know what your objects contain and are capable of.

Kirk out.

Share this post :

PowerShell Quick Tip: When you store an array, make sure it is an array

It is not uncommon to want to store the results of a command in an array so that you can refer to the contents of that array later in your script.  This is often done by simply assigning the results of a command to a variable.  For example, if you were importing the contents of a csv file into an array you might do it like this:

$collection = Import-Csv C:\newusers.csv

Commands like this will work forever as long as you have a csv file in the appropriate location.  Occasionally though you may find that a script containing this command just stops working.  It might not make sense to you at the time because the script hasn’t changed, so what’s the problem?

The problem is that in the above command there is no guarantee that $collection will actually store an array of objects because there is no guarantee that Import-Csv will return more than one object.  In fact, there is no guarantee that Import-Csv will return any objects at all.  If the C:\newusers.csv file is generated by some other tool, it is possible that it might contain 0, 1 or more entries.  When it contains 0 or 1 entries, the $collection variable will either be $null or it will contain one object representing the single entry in the C:\newusers.csv file, respectively.  If further down in your script you’re always treating $collection as if it is an array and using indices to access elements in the array, your script will suddenly start failing with errors.

Fortunately there is a way to ensure that the results of any command are stored in an array.  All you need to do is wrap the command that returns the object(s) in array enclosures, like this:

$collection = @(Import-Csv C:\newusers.csv)

If you take this approach in your script and then later when you run your script C:\newusers.csv only contains one entry or perhaps doesn’t contain any entries at all, your $collection variable will still be an array so that you can check the size using the Count property ($collection.Count), access individual values using indices (e.g. $collection[0]), etc.  This is a much safer practice to take when writing your scripts so that you can make sure that you have an array when you store an array.

Of course if you are dealing with very large collections of objects that aren’t already loaded in memory an even better practice is to avoid storing the entire collection in memory altogether by using pipelines and cmdlets like ForEach-Object to process the items in the collection, but I wanted to make sure you were aware of how you should properly handle and store arrays in variables in your scripts so that you’re not facing a script long after it has been written and asking yourself why it suddenly stopped working.  There are plenty of other reasons why a script could stop working at some point but at least now you’re armed with the knowledge required to avoid one of those possibilities.

Kirk out.

Share this post:

PowerShell Quick Tip: Setting AD object attributes with ScriptBlock parameters

In PowerShell you can pass pipeline data to parameters that are not configured to accept pipeline input using ScriptBlock parameters.  This has been discussed before and it is well worth your time to make yourself familiar with that capability of PowerShell because it allows you to create true one-liners in places where you might think you cannot.  Basically it boils down to this: you can pass a script block into parameters in a cmdlet that is not of type script block and within the script block you provide the $_ variable will contain the object that was just passed down the pipeline.  The script block will be evaluated first, and then the result will be passed into the cmdlet parameter.

Still, even with that knowledge in hand people often trip over the ScriptBlock parameter syntax when using a parameter that takes a hash table (aka an associative array) as input.  The most common example I can think of where this is encountered is in the Quest AD cmdlets.

Many of the Quest AD cmdlets have a parameter called ObjectAttributes.  This parameter serves two purposes: in Get cmdlets it is used to define the values you want to filter on; in Set and New cmdlets it is used to define the values you want to assign to specific attributes.  Not consistent, I know, but that’s a whole other discussion.  In both cases the ObjectAttributes parameter is a hash table and to use it you simply need to define a hash table that matches attribute names with values.  Here’s an example showing how users trip over the syntax without realizing it:

Get-QADComputer Comp1 `
    | Set-QADObject -ObjectAttributes ` 
        @{userAccountControl=$_.userAccountControl -bxor 2}

This one liner was designed to enable or disable a computer object in AD.  It will run without raising an error, and it will even enable or disable the computer object, but it will not work like you might expect.  After running this command the userAccountControl attribute (which contains many flags, not just a flag for enabled/disabled state) will not be properly configured.  Why?  It looks like it is properly using a ScriptBlock parameter, but it is not so the $_.userAccountControl value will either evaluate to 0 if the $_ variable is null or if it does not have a userAccountControl property, or it will contain the userAccountControl value from whatever object the $_ variable contained before you called Get-QADComputer in the first stage of this pipeline.  Nothing in this command instructs PowerShell to treat the ObjectAttributes parameter as a ScriptBlock parameter.  So what’s missing?

The most important thing to remember about ScriptBlock parameters is that they always must be surrounded by script block enclosures (“{“ and “}”).  Otherwise they are not script blocks.  In our example above it looks like the parameter is surrounded by the proper enclosures, but that’s not true.  It’s surrounded by hash table enclosures (“@{“ and “}”), and this is what trips people up when working with this syntax.  To make this be properly treated as a ScriptBlock parameter, we need to surround the hash table with curly braces, like this:

Get-QADComputer Comp1 `
    | Set-QADObject -ObjectAttributes ` 
        {@{userAccountControl=$_.userAccountControl -bxor 2}}

That makes PowerShell recognize that we have a ScriptBlock parameter and the $_ variable within it properly evaluates to the object that just came down the pipeline.  No ForEach-Object or temporary variables required.  It seems simple enough when explaining it but you’d be surprised how many people get tripped up on this syntax.

If you want to see some forum discussions where this has been an issue for others, you can go here or here.

Kirk out.

Share this post:

PowerShell Quick Tip: Create aliases to facilitate invocation of PowerShell script (ps1) files

I was just reading Steve Murawski’s latest blog post which contains a script called ConvertTo-Function that allows you to generate a function from a script file.  The intent is to allow you to have scripts you don’t call as often, and therefore don’t load into your profile, but when you need them you can use this script to easily generate functions that wrap them so that you don’t need to think about the path beyond the initial call to the ConvertTo-Function script.  That’s one way to facilitate this, but I have something I use quite commonly in my own environment that works much better for me for this scenario: aliases.

Aliases can reference a path to a script file.

This capability is often overlooked because aliases are most commonly used and thought of as terse command names.  Using them to reference a path to a script file is quite useful because it allows you to keep all of your scripts, complete or in development, in a common location.  You can control which ones are loaded in your profile by only creating aliases for those scripts.  Alternatively, for scripts that you really don’t use very often, you could have a function in your profile to create the aliases to those scripts on an as needed basis (which sounds a little like the Add-Module capabilities in PowerShell v2).  In either case, using aliases to reference script files is very handy because you don’t need to worry about whether the scripts you want to invoke are in a folder in your path environment variable or not.  You simply need to create an alias to the full path and then you can use that alias to invoke your script.

For example, assuming you had a script with the path C:\MyScripts\Miscellaneous\Do-Something.ps1, you could invoke it using the absolute or relative path, or if C:\MyScripts\Miscellaneous happened to be in your path environment variable you could simply invoke it using the Do-Something command.  But if you don’t have your script path in your path environment variable and/or you don’t want to change that variable, aliases give you the same capability on a temporary basis for your current PowerShell session (and only in your current scope by default if you want it really temporary).  Using our example script path, all you would have to do is create a new alias with this command:

New-Alias Do-Something C:\MyScripts\Miscellaneous\Do-Something.ps1

That allows you to invoke your script any time you want to by simply using the Do-Something alias, as long as it is created and visible in the current scope.

Kirk out.

Share this post:

PowerShell Quick Tip: How to retrieve the current line number and file name in your PowerShell script

(aka How to add C-style __LINE__ and __FILE__ macros to PowerShell)

When troubleshooting PowerShell scripts, it can be useful to log, store or output the current line number and and file name position so that you know where your script was when something happened.  This information is given to you automatically when there is an error, however if you’re not facing a PowerShell error but you have a problem with the logic in your script, you need to manually retrieve it from PowerShell.

The MyInvocation variable was designed for this purpose, providing you with details about the invocation of the current script or function as well as details about the current command that you are executing.  The trouble is that when you use $MyInvocation directly in a script file, the details it provides are about the invocation of the script, meaning the command used to invoke the script, and not about the current line within the script.  Fortunately, though, as with many things PowerShell there is a way.  To be able to use $MyInvocation to retrieve details about the current line in a script, you need to wrap it in a function and call that function.  When you do this, $MyInvocation will give you details about the command used to call the function that contains it, and if that command is inside a script file the details will include the script file name as well as the line number of the command.

Armed with that knowledge, you can create a few useful functions in your profile, and they will allow you to retrieve the current file name and line number from within any script you use.  Here are my interpretations of those functions:

#region Script Diagnostic Functions

function Get-CurrentLineNumber {
    $MyInvocation.ScriptLineNumber
}

New-Alias -Name __LINE__ -Value Get-CurrentLineNumberDescription Returns the current line number in a PowerShell script file.

function Get-CurrentFileName {
    $MyInvocation.ScriptName
}

New-Alias -Name __FILE__ -Value Get-CurrentFileName -Description Returns the name of the current PowerShell script file.

#endregion

A few good uses for these functions might be:

  1. In complicated scripts where you want to verify the order of execution of certain lines of script.
  2. When you are returning a value in multiple locations and you would like to simply know which location you returned from while troubleshooting without stepping through any lines in your script.

I hope this is useful for you.  I just started using these functions and they are already coming in quite handy for me.

Enjoy!

Kirk out.

Share this post:

PowerShell Quick Tip: Use the command argument last when calling PowerShell.exe

Have you ever tried calling PowerShell.exe with the -NoExit argument and wondered why PowerShell is still exiting when it’s done your script?  For example, if you want to quickly launch a new clean PowerShell session that immediately runs a script, you might run this:

PowerShell.exe -Command “your script” -NoExit

When you do this, PowerShell still exits after it is done with your script.  Also, if you were paying attention it likely reports an error that references “NoExit”.  It seems that this happens because PowerShell.exe looks at all arguments after -Command and treats them as the command you want to execute in your new PowerShell session.

The solution to this problem is simple.  Always make -Command the last named argument in your argument list.  Looking at the above example, that means rearranging the command to look like this instead:

PowerShell.exe -NoExit -Command “your script

This will execute your script and leave PowerShell open for you to work with it afterwards.

Kirk out.

Share this post: