Monday, November 30, 2009

A Great Write-Up on WMI Events and PowerShell 2.0

I haven't been inspired to do a full post in a while, but I stumbled across this article by Trevor Sullivan today, and I thought I'd point it out for those of you that are interested.

Friday, October 30, 2009

Working with CSV Files

It's been a while since I had an update to the blog, and I've been waiting for something to jump out at me. Today I read on the news that the White House has released some visitor logs to the public, and they are making them available for download as a CSV file, and it piqued my interest, so I downloaded the file.

PowerShell v1 and v2 both have a module called Import-CSV that makes it ridiculously easy to work with CSV files. They will slurp up the file and output an array of objects, one per row, with properties that correspond to the columns in the file.

Let's import the file and look at a record:

PS> $visitors = Import-Csv .\10-30-waves-posting.csv
PS> $visitors[-1]


NAMELAST : ZIMPHER
NAMEFIRST : NANCY
NAMEMID : L
UIN : U81194
BDGNBR : 72400
ACCESS_TYPE : VA
TOA : 4/1/20091:45:42PM
POA : D0102
TOD : 4/1/20095:50:21PM
POD : D1
APPT_MADE_DATE : 3/25/20095:07:33PM
APPT_START_DATE : 4/1/20092:00:00PM
APPT_END_DATE : 4/1/200911:59:00PM
APPT_CANCEL_DATE :
Total_People : 3
LAST_UPDATEDBY : J7
POST : WIN
LastEntryDate : 3/25/20095:08:57PM
TERMINAL_SUFFIX : J7
visitee_namelast : DOUGLAS
visitee_namefirst : DEREK
MEETING_LOC : OEOB
MEETING_ROOM : 459
CALLER_NAME_LAST : WILKINS
CALLER_NAME_FIRST : ELIZABETH
CALLER_ROOM :
Description :

Interesting. I wonder, who had the most visitors (I snipped out the middle of the results)?

PS> $visitors |
Group-Object visitee_namelast |
Sort-Object Count |
Select-Object Count, Name

Count Name
----- ----
1 ANDREW
1 LONG
1 BINNIX
1 BREWER
...
7 'SUMMERS'
7 JARRETT
8 'POTUS/FLOTUS'
8 'EMANUEL'
9 POTUS/FLOTUS
9 'DOEBLER'
10 DOEBLER
10 'TCHEN'
12 TCHEN
42 'POTUS'
56 POTUS
69 OFFICE
211

Uh oh. There's something funky about this file. There are empty lines, and it's inconsistent about when it puts quotes around text fields. I'm pretty sure we don't have any fields with commas in them, so let's try stripping out any lines that only have whitespace and/or commas in them and then strip out single quotes from the rest of the lines:

PS> Get-Content .\10-30-waves-posting.csv |
?{$_ -notmatch '^(\s|,)*$'} |
%{$_.replace("'", '')} > wh.csv

** Note: that regex after -notmatch above can be read as:
^ start at the beginning of the string
(\s|,)* zero or more whitespace or comma characters
$ the end of the string

There. Now we can try that again, using our new file (snipped for length again).

PS> $visitors = Import-CSV .\wh.csv
PS> $visitors |
Group-Object visitee_namelast |
Sort-Object Count |
Select-Object Count, Name
Count Name
----- ----
1 SHAH
1 LONG
1 PETER
1 ANDREW
...
19 DOEBLER
22 TCHEN
69 OFFICE
98 POTUS

So now we know that the person with the largest number of visitors was the President of the United States of America. Hardly surprising, but you can easily explore this data with PowerShell. Here are a few more queries you might try:

Look at the records of the visits to the President

PS> $visitors |
?{$_.visitee_namelast -eq 'POTUS'}

How many visits occurred in July?

PS> $visitors |
?{$_.APPT_START_DATE -match '^7/'} |
sort APPT_START_DATE |
select NAMELAST, NAMEFIRST, APPT_START_DATE

What was the description given of the appointments for meeting the President?

PS> $visitors |
?{$_.visitee_namelast -eq 'POTUS'} |
select APPT_START_DATE, NAMEFIRST, NAMELAST, Description |
sort APPT_START_DATE


Hopefully this gives you a good idea of how you can manipulate CSV data using PowerShell using Import-CSV and a little filtering.

Thursday, September 3, 2009

A Regular Expression Cheat Sheet for PowerShell

Okay, so there's really not much that is PowerShell-specific about this cheat sheet, but I wrote it up in response to a post on microsoft.public.windows.powershell, and I thought I'd share it here, since I know a lot of new PowerShell users haven't been exposed to Regular Expressions very much.

This is not meant as an exhaustive reference on regular expressions, but just something that may be helpful if you get a little stuck. I hope you find it helpful.

1. What kind of character is it?

[] - any of the characters inside the brackets will match
(use the dash to indicate a range)
Examples: [a-z] will match any letter.
[aeiou] will match any vowel
\w - "word" characters. Basically matches [a-z0-9_]
\s - "whitespace" characters. matches spaces, tabs, etc.
\d - any digit. Basically matches [0-9]
\t - a tab character
. - any character


2. How many characters?
(the below appear after the character class)

{x} - matches x number of characters
{x, y} - matches minimum x number of characters, maximum y characters
Examples: \d{4} matches 4 digits
* - matches ZERO or more of the character (as many as possible)
+ - matches ONE or more of the character (as many as possible)
*? - matches ZERO or more characters (as few as possible)
+? - matches ONE or more characters (as few as possible)


3. Where is the character?

\b - matches a word boundary, without actually absorbing any characters
^ - matches the beginning of a string
$ - matches the end of a string


4. Grouping

() - any characters between the parentheses will be their own group
(try checking the value of $matches after using -match)
| - a pipe character is the OR character
Example: (one|two) will match the word "one" or the word "two"

Wednesday, August 26, 2009

Add a List of Users to the Local Admins Group

Just a quick update here, and another real-world example of where the Split() method of a string can come in handy for day-to-day tasks.

Today I had to add a list of about 20 individual usernames as administrators to a particular machine. Someone sent me the comma-separated list in an IM, and it took me about ten seconds:

PS> 'user1,user2,user3,user4,user5,user6,user7,user8,user9,user10'.split(',') |
>> %{net localgroup administrators $_ /add}

The command completed successfully.

The command completed successfully.

The command completed successfully.

The command completed successfully.

The command completed successfully.

The command completed successfully.

The command completed successfully.

The command completed successfully.

The command completed successfully.

The command completed successfully.

Wednesday, August 12, 2009

Use PowerShell to Get Local Group Members from a Remote Computer

I had a friend ask me recently how to get a list of administrators from a server.

"That's easy" I thought. "You just have to... um... actually..."

It turns out that this can be frustratingly difficult in PowerShell, so I wrote this module to make it easier when I'll need to do this in the future. It's comprised of three functions:
  • Add-NoteProperty: About 90% of the time when I want to create a custom PSObject and add some properties, they're all NoteProperties. This function makes adding a property easy, like so: Add-NoteProperty $my_object 'PropertyName' $property
  • Get-COMProperty: This is a kludgy hack to get around the fact that members of groups gotten through ADSI get returned as __ComObject objects, and you have to call the InvokeMember() static method of the class in order to get at their properties. Now I can just do this: Get-COMProperty $com_object 'PropertyName'
  • Get-LocalGroups: This is the function that we needed. It returns a list of custom PSObjects representing the local groups on a server, and each one has a property called Members that is a list of custom PSObjects for each member, including the Name, Domain, and ADSPath. From there, you can use whatever method you want to the object you want, whether it's using Get-QADUser for domain users, or whatever, for example: Get-LocalGroups computername.
If you're using Powershell v2 CTP3 or higher, you can use help on each of the functions to see examples if you forget. You can download the module from Poshcode.org here.



Saturday, August 8, 2009

String Manipulation: Splitting and Joining Strings

I think it's time to get back to some more basic things, so let's get to it.

The split()method of a string object lets you define a delimiter and "split" the string up into multiple pieces, which are returned as an array.

Let's say I do this:

PS C:\Users\tojo2000> $string = "one two three four"
PS C:\Users\tojo2000> $string.split(" ")
one
two
three
four


As it turns out, though, split() splits on a single whitespace character by default.

Observe:

PS C:\Users\tojo2000> $string = "one two three four six"
PS C:\Users\tojo2000> $string.split()
one
two
three
four

six


What's the practical use for this? Let's say you have a tab-separated values file called people.txt that looks like this:

Doe John California
Doe Jane Texas
Neuman Alfred Nebraska


And you want the output to be First Last, Location. You could do it like so:

PS C:\Users\tojo2000> Get-Content people.txt |
>> %{$data = $_.split("`t"); Write-Output "$($data[1]) $($data[0]), $($data[2])"}
>>
John Doe, California
Jane Doe, Texas
Alfred Neuman, Nebraska


But what if you have a file like this called people2.txt (let's say the first part is some kind of ID)?

2323:Doe John California
827:Doe Jane Texas
982982:Neuman Alfred Nebraska


Now you have two delimiters, but the split() method of a string object takes a string as an argument, so you would have to split twice in order to get each part of the string split into an array. If only there was some way to split on a regular expression... Oh, wait, there is.

PS C:\Users\tojo2000> Get-Content people2.txt |
>> %{$data = [regex]::split($_, '\t|:'); Write-Output "$($data[2]) $($data[1]), $($data[3])"}
>>
John Doe, California
Jane Doe, Texas
Alfred Neuman, Nebraska


('\t|:' is a regular expression for a tab character or a colon)

What if you have an array, and you want to make a string out of it? Then you can join the strings together using join(). Now there are two ways to join a list of strings, depending on whether you're using PowerShell v1 or v2.

The first way works in both v1 and v2:

PS C:\Users\tojo2000> $buncha_strings = 'one', 'two', 'three', 'four', 'five', 'six'
PS C:\Users\tojo2000> [string]::join(' and a ', $buncha_strings)
one and a two and a three and a four and a five and a six


So what practical use would there be? For a completely contrived example, maybe you want to create a new version of people2.txt called people3.txt that uses pipe characters to separate the fields. We'll split the strings up into an array just like before, but then we'll join them back together the way we want them.

PS C:\Users\tojo2000> Get-Content people2.txt |
>> %{$data = [regex]::split($_, '\t|:'); [string]::join('|', $data)} > people3.txt
PS C:\Users\tjo2000> Get-Content .\people3.txt
2323|Doe|John|California
827|Doe|Jane|Texas
982982|Neuman|Alfred|Nebraska


There is one more way that you can join a list of strings, but this method only works in v2: the -join operator. It works the same way as the other join(), but the syntax is slightly different.

For example, here is how you would join a list of strings by tab characters:

PS C:\Users\tojo2000> $list_of_strings -join "`t"


So to wrap this all up, let me give a real-world example. I had a SQL query that I wanted to run that would retrieve the SCCM information for a specific list of computers. I needed to update the list, and I had the computers in a file called machines.txt, with one name per line.

Here's how I slurped up the file to create the new query string:

$query = @"
SELECT * FROM v_R_System
WHERE Netbios_Name0
IN ('{0}')
"@ -f ((Get-Content machines.txt) -join "', '")

Saturday, July 4, 2009

Super Duper Over-Engineered Egg Timer


This was my submission for Event 10 Beginner of the 2009 Scripting Games this year. I usedPrimalForms to design the form and generate the boilerplate code. It's a really great tool.

I added the timer functionality myself. The Windows.Forms.Timer object is very simple. You tell it how frequent to set the ticks, set it to enabled or disabled, and call Start() to kick it off. The Timer itself doesn't keep track of how long it has been going, you do that by adding an event handler for the Tick event. You do that as seen on line 5 below, by calling add_tick() on the Timer object and passing it a script block that will be called every time the event fires. As far as I can tell this should work for any Windows.Forms object, calling add_event(), where "event" is the name of the event you are adding a handler for. The interval is in miliseconds, so in this case
the scriptblock $timer1_OnTick will be called once every second.

I went ahead and gathered the relevant lines in one place below so it's easier to see what I did. They're kind of spread around in the actual script.

  1. $timer1 = New-Object System.Windows.Forms.Timer
  2. $timer1.Enabled = $true
  3. $timer1.Start()
  4. $timer1.Interval = 1000
  5. $timer1.add_tick($timer1_OnTick)




Tuesday, June 30, 2009

Recursively Getting All Folder Sizes

This started out as my entry for Event 8 in the Microsoft Scripting Games 2009 (see more entries here), but it's useful, so I think I'll keep it. This function uses recursion to work its way down the directory tree and get all folders and their sizes, and outputs them as psobjects for easy sorting, etc. It's not the fastest thing in the world, but it does the trick. Make sure to check out the examples in the help documentation.





Tuesday, June 9, 2009

The 2009 Scripting Games!

So the 2009 Scripting Games have begun! There are 10 events this year, and the theme is decathalon. This is my first year participating. The next few posts will be my solutions. Check out the Script Center for updates and event details.

Sunday, May 31, 2009

Using Strict Mode with Set-PSDebug

If you've used Perl in the past, then you're probably familiar with "use strict" (if you've been using Perl without "use strict", then head over here Note: the scoping issues don't apply to PowerShell).

By default, PowerShell is very accommodating. If you tell it to echo the value of $chouderhead, it will happily echo... NOTHING! There is no variable called $chouderhead, you misspelled $chowderhead. PowerShell doesn't like to make waves, though, so it happily creates a new variable named $chouderhead and starts using it. This can lead to very hard-to-debug issues.

You can fix this By adding this line to your profile (or just typing it in at the prompt):

PS> Set-PSDebug -strict

Now if you try to use a variable that hasn't yet had a value assigned to it, you'll get an error like this:

PS> $chouderhead
The variable '$chouderhead' cannot be retrieved because it has not been set.
At line:1 char:13
+ $chouderhead <<<<
+ CategoryInfo : InvalidOperation: (chouderhead:Token) [], RuntimeException
+ FullyQualifiedErrorId : VariableIsUndefined

The error is annoying, but if you've ever wasted a half an hour wondering why your script isn't outputting what you thought it would, only to realize that on line 304 you accidentally used an 'm' instead of an 'n' in your variable name and how could I miss that, it's so stupid, and now I missed dinner, and my back hurts from crouching over my keyboard staring at the screen for too long and curse the day I was born! Why, oh, why didn't I just add 'PSDebug -strict' to my profile? Why?

But I digress...

Tuesday, May 19, 2009

Get-ChildItemRecurse Update

Someone named CrazyDave made an important update to the script I made yesterday. I was checking for the specific type System.IO.DirectoryInfo. The problem with this is, it unnecessarily limits my script to being used on files and folders. Get-ChildItem makes no such distinction. It can be used on any PSDrive.

So here is the corrected version: http://poshcode.org/1115 (highlighted line is the old one)



Monday, May 18, 2009

A Get-ChildItem Wrapper That Lets You Say How Deep to Recurse

I noticed that a couple of people hit my blog that seemed to be looking for a script like this, and it turned out to be an interesting exercise.

Notice that I took the fileglob and moved it down the script rather than just using it in my call to Get-ChildItem. I had to do that because otherwise I would only have been recursing through directories that matched the pattern, and I wanted to recurse down into all directories, but only send those entries that match the pattern down the pipeline.

The function is here, for those that don't see it inline in the post: http://poshcode.org/1113



Sunday, May 3, 2009

Use PowerShell to Recursively Get Group Members

I've noticed looking at my StatCounter logs that a few people found my post on recursion while looking for a way to recursively get group members in PowerShell, so I whipped up this script.

Note that it depends on the AD Cmdlets from Quest, and that it works in much the same way as the example. Whenever it finds that one of the group members is a group, it calls itself on that group and adds the result to the list of users.

Also note the technique of using a hashtable to keep a list of unique users. It's common to store a list in an array and use use -contains and -notcontains to find out if an item already exists, but this is tremendously inefficient compared to using a hashtable as an index, especially as the list of users gets longer, because it has to keep cycling through the list over and over. It's a trick I picked up in Perl that works equally well here.



Saturday, May 2, 2009

An Excellent Post on Bitwise Operators

If you find yourself working with Microsoft APIs for any length of time, you'll find yourself having to deal with bitmasks and bitwise operations. Mark Schill over at String Theory has an excellent description of what they are and how to work with them.



Friday, May 1, 2009

Advanced Functions: Using Values from the Pipeline (2.0 CTP3)

Note: The following code will only work on Powershell 2.0 CTP3 or later.

A while back I showed how you can use $input as a parameter to a function in order to use it in the pipeline. Unfortunately that technique has the side effect of making PowerShell stop and gather up all variables that are being sent down the pipeline into $Input and then passing it to the function. Ordinarily it doesn't matter much, but if you are processing a lot of data, say the results of a SQL query, you may end up using up massive amounts of memory and watch your computer grind to a halt while the memory manager swaps a billion times a second (okay, I made that number up).

The ValueFromPipeline Parameter Property

As the name suggests, this property indicates whether or not a parameter can take a value from the pipeline. Just set it to $true when declaring the variable (you'll see an example at the bottom of this post).

Doing that alone will not give you the effect you're looking for, though. It will mysteriously process the first item passed down the pipeline...and then suddenly stop. This was very frustrating when I first came across it, and left me scratching my head, until a post on the microsoft.public.windows.powershell group happened to mention the critical missing piece in an unrelated discussion.


BEGIN, END, and PROCESS Blocks

The first thing you need to know is that if you want to take a value from the pipeline and use it, then you need to add in the optional PROCESS Block. Every function essentially has a BEGIN, END, and PROCESS scriptblock in it:
  • BEGIN - This scriptblock is run the first time the function is launched
  • PROCESS - This scriptblock is run each time the function receives input
  • END - This scriptblock is run after all input has been processed
Here's the important part: explicitly declaring these blocks is optional, but if you don't use them, then the body of your function executes in the END Block. That's why it was only executing once.

As an example of a script that uses these blocks, take a look at this little ditty I whipped up for work (I highlighted the important parts):




Tuesday, March 31, 2009

SMS Module Updated

I updated the SMS Module (old post here) for PowerShell v2. I'm using it with v2 CTP3. Now you can type "help " to get help on these functions, and check out the old post for more examples of how you can use the Get-SmsWmi and Find-SmsId functions.

Functions included are:
  • Get-SmsWmi - Makes it easy to get WMI objects from the SMS Provider. (Note: call this function with no parameters to get a list of acceptable class nicknames)
  • Find-SmsId - Look up SMS WMI objects by ResourceID, CollectionID, PackageID, or AdvertisementID
  • Get-SmsCollectionTree - Inspired by tree.com from DOS, prints out the Collection structure of your site, with CollectionIDs and names.
  • Approve-Client - Marks an SCCM client as Approved in the SCCM DB.
Don't forget to set the $default_wmi_provider_server and $default_site variables at the top of the module to match your environment.



Wednesday, February 18, 2009

Get-ChildItem (dir) Results Color-Coded by Type

I always liked the way you can color code your directory items in Linux. I always set alias ls='ls -al --color' the minute I log into a system. It makes it easier to scan results and instantly know some extra data about the items in a directory.

I decided that I wanted to be able to do this in PowerShell. If you do a few Google searches you'll see I'm not the first, and it's not without it's quirks, but I decided not to worry about being true to the Linux form and just go with the spirit of what I wanted to get out of it. I've included the code below. There are a couple of things of note, though it's mostly waht I wanted:
  • I would rather if the header at the top wasn't always the same color as the first item, but I can't think of any way around that.
  • Rather than coloring results by permissions, I've colored them by type, with four major types: Directory, Executable, Compressed, and Text.
  • If you use sort or select you'll lose the color, but otherwise the color change should last down the pipeline for each obect.
  • This was made to be used with v2.0 CTP3 of PowerShell. If you want to use it with earlier versions you have to remove the documentation block at the beginning of the function, but you also lose the ability to use the help command to find out more about the function.
  • Note that I compile the Regular Expressions at the beginning. If I just used -match to check the regular expressions for each file, I would add a lot of extra overhead, because the regular expressions would be compiled each time a file was checked. This boosts performance, and there is an analogue in each language that supports regular expressions.