I have had problems in my very long scripts with Write-Progress. When writing the script you add in several Write-Progress statements and give them a percentage, the problem is that the percentage you give it is likely a number that you are typing in and hard coding. This is a problem because if you add to the code or move the code around you have to go back and change every one of these items.

In a recent script I wrote that was just over 300 lines I had to repeat this process 5 times which was really frustrating, so I thought up a way to make sure I don’t have to do it again.

Now, I should state beforehand that this process only works if each write-progress is executed once and only once. Any changes to that means you may need to add some other intelligence, but in general I think this is a good starting point.

The first thing you want to do is read the script file that you are executing to get a count of the number of lines with a “Write-Progress” in them. Then store a tracking number as a global variable so that all functions can see it. Do this at the start of the script.

Here is my single line to do all of the above:

1
$ProgressIndicators = $($Global:I=0; $(GC $MyInvocation.InvocationName | ? {$_ -Match "Write-Progress"}).count -1)

So, there is a whole lot of learning packed into a small area here. Let me cover the confusing sections. First naming a variable $Global:I will create a global variable called “I” that is accessible from all PowerShell scopes. As well, there is a “;” after this portion to execute this as a separate line of code. Since there is no output, this will not become part of the variable when run like this. For more information on scopes visit http://technet.microsoft.com/en-us/library/dd315289.aspx.

The command GC is a built-in alias for Get-Content.

The object property “$MyInvocation.InvocationName will provide the file path of the script that is currently being executed. So, in effect, we are reading the entire script that is currently running.

Then we pipe the script contents to ? which is a built-in alias for Where-Object. Then we look for a match on “Write-Progress”. You’ll notice that this whole section is surrounded in $() which “Instantiates” a variable for this this output. Then we can find the number of lines with the .count property. After that, I subtract 1 so it doesn’t count itself.

All of this means that the variable $ProgressIndicators will now contain the number of times “Write-Progress” appears in the script, no matter what script you place it in.

So now all we need is for Write-Progress to read these two values when it run and increment our global tracking variable.

1
2
3
4
5
6
Write-Progress -Activity "Making Progress" -Status "Test 1" -PercentComplete $($($Global:I++;$Global:I) / $ProgressIndicators * 100)
Sleep 1
Write-Progress -Activity "Making Progress" -Status "Test 2" -PercentComplete $($($Global:I++;$Global:I) / $ProgressIndicators * 100)
Sleep 1
Write-Progress -Activity "Making Progress" -Status "Test 3" -PercentComplete $($($Global:I++;$Global:I) / $ProgressIndicators * 100)
Sleep 1

So, as you can see I use the same techniques described above to increment the variable and then do some basic match to get a percentage of total progress.

You can throw this code into any script to give you a dynamically updating progress indicator. There are two things to keep in mind. If you have a loop that runs a write-progress command over and over then it will continue to increment and break this. You could remove the $Global:I++ portion or make a sub progress bar to correctly display the progress of the loop. Also, you might skip a write-progress line due to an IF statement. If so maybe you could increment the value of $Global:I in an ELSE statement.

 

This is the second post in my AD without Quest series. I am covering individual functions that can be combine to produce a wide variety of scripts. Today I am going to be covering how to connect to AD to read an object ADSPath. The ADSPath is basically the LDAP string to connect to that object. Once you have an object’s LDAP path it is very easy to work with the object.

When searching in AD all you need to do is use the directory searcher object and continue to narrow down the filter. There are some slight changes between searching for a user, computer and group, so I’ll cover each.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 Function Get-UserADSPathbyName{
    Param([STRING]$User)
   
    $Searcher=New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.Filter="(&(SamAccountName=$User)(objectcategory=user))"
    $Searcher.PageSize = 1000
    $Results=$Searcher.FindAll()
    If(!$?){"AD searcher failed on $User"}

    If ($Results.Count -eq 1){
        $Results[0].properties.adspath
    }Else{
        Return $false
    }
}

On the line with $Results[0].properties.adspath it is important to note that the “adspath” portion is case sensitive. I really have no clue why that is the case. If you know, drop me a comment to help me out!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 Function Get-ComputerADSPathbyName{
    Param([STRING]$Computer)
   
    $Searcher=New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.Filter=(&(CN=$Computer)(objectcategory=computer))
    $Searcher.PageSize = 1000
    $Results=$Searcher.FindAll()
    If(!$?){"AD searcher failed on $Computer"}

    If ($Results.Count -eq 1){
        $Results[0].properties.adspath
    }Else{
        Return $false
    }
}

So as you can see, a couple of small changes will convert the function for use with computers instead of users. One final function converts this again for use with groups.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 Function Get-GroupADSPathbyName{
    Param([STRING]$Group)
   
    $Searcher=New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.Filter=(&(CN=$Group)(objectcategory=group))
    $Searcher.PageSize = 1000
    $Results=$Searcher.FindAll()
    If(!$?){"AD searcher failed on $Group"}

    If ($Results.Count -eq 1){
        $Results[0].properties.adspath
    }Else{
        Return $false
    }
}

So there they are, but what can you do with them? There is a great variable type in PowerShell defined with the ADSI tag. Once a LDAP string is defined as and ADSI variable you can interact directly with that object by reading any property you want, and even changing them.

1
2
3
4
5
6
7
$UserLDAP = [ADSI]$(Get-UserADSPathbyName “MyUserName”)
$UserLDAP.extensionAttribute1
>MyOldValue
$UserLDAP.Put(“extensionAttribute1”,”MyNewValue”)
$UserLDAP.SetInfo()
$UserLDAP.extensionAttribute1
>MyNewValue
 

I’ve decided to start an Active Directory series. I have discovered that when you search online for any AD related code everything points back to the Quest tools. I prefer to do everything natively and not rely on a third party tool kits that have to be installed. As an admin, I understand that we do rely on our workstations, but when you start needing to install third party tools on server you are entering dangerous ground. I wouldn’t want to explain that Exchange went down because I installed some tool that locked the server.

I have been using PowerShell since its inception and I still haven’t found an instance where I need to use the Quest tools. Sometimes it might be easier, but once you’ve created a well written function, it’s easy to copy and paste it into your next script. So I am going to base this series off of individual functions that can be added to scripts to work with AD.

We’ll start with the basic ones. The following functions can be used to pull all the server names and user name out of Active Directory. This can be modified to pull whatever property you’d like, but I find that either account name or ADSPath (the LDAP string) are the most useful. I use some variation of these in almost every script I make.

First, let’s look at pulling all AD servers. Now these are only for the domain that you are running it from. There is some extra work to do to change domains, but I’ll cover that in a later blog post. The basic idea is to create a directory searcher, then set a filter (or you will return every object in AD). Then you need to set the page size. That, basically, tells the searcher how many objects it can return in a single search, so if you set it to 1000 and you have 8000 total objects, it will run a total of 8 searches. This step can be skipped if you have a very small domain, but AD has a built in limit to how many objects can be returned in a single search. This value can be set by the admin, but 1000 works with default domains. Finally, you run the search and then return the property you want from the function.

1
2
3
4
5
6
7
8
 Function Get-ServerList{
    $Searcher=New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.Filter="(&(objectcategory=computer)(operatingSystem=*Server*))"
    $Searcher.PageSize = 1000
    $Results=$Searcher.FindAll()

    $Results | % {$_.Properties.name}
}

So, that’s it. You can drop this into your script and then run it with $servers = Get-ServerList to get a list of all your server names. This function runs very quickly and can be put into any script to make things really easily.

However, part of what I am trying to get across is that these functions can be quickly changed to suite your needs. So this is the function very slightly changed to pull all the users instead. In this case the PageSize parameter really comes into use.

1
2
3
4
5
6
7
8
 Function Get-UserList{
    $Searcher=New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.Filter="(objectcategory=user)"
    $Searcher.PageSize = 1000
    $Results=$Searcher.FindAll()

    $Results | % {$_.Properties.samaccountname}
}

More AD entries to come! You can find them via my blogs tags to the right.

 

With the scripting games coming up, I noticed that they will be adding stars to your work based on the addition of script help blocks that are compatible with the PowerShell “Comment Based Help”. Basically, this is a form of writing a comment block that works in conjunction with the get-help cmdlet that is included with PowerShell.

With this post I am hoping spread the information that this is possible just as much as the how to do it. I think that most script writers don’t even know it’s possible, and I’ll be honest that while I’ve know that you can do it, I don’t include in all of my own scripts.

So, you can add comment based help either to a script or to a function. It is my opinion that you do not need to add the help to a function within a script as no one will be able to run a get-help against it; however, using the format provided to label your functions is probably a good idea.

So, there are two ways to make a comment block and three places to put them.

You can start all the lines in a block with # or you can surround all the lines with <# ...comments... #>. Either form is fine, but the later might be easier for large block; however, the former might be easier to follow if browsing the code. I typically like to start my lines with # as I can quickly see where all the comments start and stop.

As far as where to place them, you can put them at the start of a function or script, at the end of a function or script,or just before the start of a function. Line breaks become very important as spaces between comments and their attached objects will detach them.

Now the comments need to follow the format:

1
2
3
4
5
# .KEYWORD
#  Content
#
# .SYNOPSIS
# This is an example of comment based help.

So what kind of keywords are there? This is taken from the help file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    .SYNOPSIS
        A brief description of the function or script. This keyword can be used
        only once in each topic.

    .DESCRIPTION
        A detailed description of the function or script. This keyword can be
        used only once in each topic.

    .PARAMETER  <Parameter-Name>
        The description of a parameter. You can include a Parameter keyword for
        each parameter in the function or script syntax.

        The Parameter keywords can appear in any order in the comment block,but
        the function or script syntax determines the order in which the parameters
        (and their descriptions) appear in Help topic. To change the order,
        change the syntax.
 
        You can also specify a parameter description by placing a comment in the
        function or script syntax immediately before the parameter variable name.
        If you use both a syntax comment and a Parameter keyword, the description
        associated with the Parameter keyword is used, and the syntax comment is
        ignored.

    .EXAMPLE
        A sample command that uses the function or script, optionally followed
        by sample output and a description. Repeat this keyword for each example.

    .INPUTS
        The Microsoft .NET Framework types of objects that can be piped to the
        function or script. You can also include a description of the input
        objects.

    .OUTPUTS
        The .NET Framework type of the objects that the cmdlet returns. You can
        also include a description of the returned objects.

    .NOTES
        Additional information about the function or script.

    .LINK
        The name of a related topic. Repeat this keyword for each related topic.

        This content appears in the Related Links section of the Help topic.

        The Link keyword content can also include a Uniform Resource Identifier
        (URI) to an online version of the same Help topic. The online version
        opens when you use the Online parameter of Get-Help. The URI must begin
        with "http" or "https".

There are even more past that, but I think that covers the main group. If you want to see them all and get more detailed information go to PowerShell and run Get-Help about_Comment_Based_Help.

As well, there are several built in sections that are generated automatically: Name, Syntax, Parameter List, Common Parameters, Parameter Attribute Table, Remarks.

To get more information and some good examples go to PowerShell and run Get-Help about_Comment_Based_Help.

 

Just though I’d spread some information about the Microsoft hosted scripting games for 2011. So far I think this year it’s PowerShell only. Microsoft wants everyone of all skills to get involved, they promise that you will learn new stuff no matter your skill level, so go try it out, you might even win something!

http://blogs.technet.com/b/heyscriptingguy/archive/2011/02/19/2011-scripting-games-all-links-on-one-page.aspx

 

My most popular article has thus far been the post over multithreading found here. As such I felt that my audience might like to see some more entries covering that topic. After all, it is a vast topic with a lot of very advanced things you can do. In this post I would like to talk about tracking the status of your various threads. In other words, how can you see a progress of what your child script is doing?

In exploring this myself, I worked with passing custom objects back to the parent which held various status messages that the parent could read in with the “receive-job” command. That actually worked, but it took a lot of code in both the parent and the child to generate and read the status from the child threads.

While working with this, though, I found that the Job object from “get-job”, a System.Management.Automation.Job, has a property called “Progress”. I then decided to look into how this actually gets filled out. And the answer eluded my research until I managed to do it, completely by accident. As it turns out, this very useful property is filled in automatically by a “write-progress” command run within the child thread. It is actually quite logical that the progress field is filled in by “write-progress”.

So, that is enough of a background on how I came about it. Let’s actually look at some code. First,I want to create a script block to run within my child job.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## Write some code for the child threads to execute
$ThreadCode = {
    $x = 0
    $time = 200
    While ($x -lt 100){
        $x++

        Write-Progress  -Activity "Testing"
            -Status "Testing"
            -PercentComplete $X
            -completed
        Start-Sleep -Milliseconds $time
    }
    Write-Progress  -Activity "Testing"
            -Status "Testing"
            -PercentComplete 100
            -Completed
    "Annnd we're done."
}

So here you’ll notice that I am basically creating a loop that will count 1 to 100 and then close. Along the way it will fill out its progress with the “write-progress” cmdlet. The one important note is that I am adding the “-completed” parameter to the “write-progress” cmdlet. That may look like a mistake in the code,but it isn’t. If you play around with this manner of getting progress from a child you will notice that when you run a “receive-job” your host thread will read all output from the child, including flashing all of the “write-progress” bars that were in the child. If you actually read the help files for “write-progress”, you’ll see that actual description of the functionality of “-complete” states that is simply hides the output bar. We need to do this so that when the parent thread runs a “receive-job” there is no odd behavior for the end user.

1
2
3
4
5
6
## Kill any current jobs in the sessions and get rid of them
Get-Job | Stop-Job
Get-Job | Remove-Job

## Start the child job and assign to a variable to save some code later
$Job = Start-Job -ScriptBlock $ThreadCode

So for this quick snippet we are just closing an remove any jobs our current session may have, and then starting our job. I assign it to a variable so we don’t have to track it down later, not that it’s hard.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
While ($Job.State -eq "Running"){
    ## Get the Child job object
    $Child = $Job.ChildJobs[0]
   
    ## Get the newest Progress Object in the child job
    $Progress = $Child.Progress[$Child.Progress.Count - 1]

    ## If the progress object is not null
    If ($Progress.Activity -ne $Null){
        Write-Progress  -Activity $Job.Name
                        -Status $Progress.StatusDescription
                        -PercentComplete $Progress.PercentComplete
                        -ID $Job.ID
    }
   
    Start-Sleep -Milliseconds 200
}

So this is the bread and butter of the parent script. It simply loops around as long as our job is running. First we need to find the child job to our parent job. I am not sure why Microsoft chose to design it like this, but each job you start actually runs commands in a child job. I have a feeling it has something to do with functionality with Windows HPC, but that is neither here nor there. Once we have our child job, we want to find the latest progress object that has been passed. After that I just check to see if the object is empty, because the first check may not have given the child thread enough time to fill this out. If there is data, then we can fill out a “write-progress” bar on our host thread. From there we just repeat until the job is done.

1
Get-Job | Receive-Job

At the end we just want to read anything the child script may actually have sent. So that covers it. It is really easy to get custom progress bars for a child process because there is a good, built in method to do it.

Following is a full example of a script which will launch three child threads at a time until there have been 10 opened total. It will show you the output of each thread as it completes and the progress bars produced by each. I even included a random speed that each child will complete so you can see some bells and whistles going.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
## Write some code for the child threads to execute
$ThreadCode = {
    $x = 0
    $time = Get-Random -Maximum 200 -SetSeed $(Get-Date).Millisecond
    While ($x -lt 100){
        $x++

        Write-Progress -Activity "Testing" -Status "Testing" -PercentComplete $X -completed
        Start-Sleep -Milliseconds $time
    }
    Write-Progress -Activity "Testing" -Status "Testing" -PercentComplete 100 -Completed
    "Annnd we're done."
}

## Kill any current jobs in the sessions and get rid of them
Get-Job | Stop-Job
Get-Job | Remove-Job

## Loop control
$x = 0

## While we are still launching threads or wait for them to close
While (($x -lt 10) -or ($(Get-Job -State "Running").count -gt 0)){
    ## While there are less than three jobs running and less than 10 have started
    While (($(Get-Job -State "Running").count -lt 3) -and ($x -lt 10)){
        Start-Job -ScriptBlock $ThreadCode | Out-Null
        $x++
    }
   
    ## Main portion - Read all jobs
    ForEach ($Job in Get-Job) {
        ## Read all children of all jobs
        ForEach ($Child in $Job.ChildJobs){
            ## Get the latest progress object of the job
            $Progress = $Child.Progress[$Child.Progress.Count - 1]
           
            ## If there is a progress object returned write progress
            If ($Progress.Activity -ne $Null){
                Write-Progress  -Activity $Job.Name
                                -Status $Progress.StatusDescription
                                -PercentComplete $Progress.PercentComplete
                                -ID $Job.ID
            }
           
            ## If this child is complete then stop writing progress
            If ($Progress.PercentComplete -eq 100){
                Write-Progress  -Activity $Job.Name
                                -Status $Progress.StatusDescription
                                -PercentComplete $Progress.PercentComplete
                                -ID $Job.ID
                                -Complete
                ## Clear all progress entries so we don't process it again
                $Child.Progress.Clear()
            }
        }
    }
   
    Get-Job -State "Completed" | Receive-Job
   
    ## Setting for loop processing speed
    Start-Sleep -Milliseconds 200
}

Get-Job | Wait-Job | Out-Null
Get-Job | Receive-Job
 

As you are typing in any given PowerShell console or in PowerShell ISE you can finish typing many things simply by hitting the “Tab” key while you are halfway through a word. This practice is refered to as “Tab Complete”. If you aren’t in the practice of using this all the time, then you really need to start utilizing it heavily for your own sake. Not only can it help you type and create code faster, but it can be useful in remembering cmdlet names or parameters when you are typing.

Things I know you can tab complete are: cmdlet names, file and directory names, third party programs, custom scripts, cmdlet parameters, variable names, object members, functions, and many more.

Another cool thing is that if you hit it too early in the word, simply hit it again to keep going through all possible entries. If you type “Get-” and then start hitting tab,you will see the names of all the cmdlets until you get the one you want. Or after you’ve typed the name of a cmdlet,type “-” and then tab to see all possible parameter names, and it even works with your own scripts!

Skipped by the command you wanted? Shift-Tab will start you back in the other direction.

 

When you are trying to run a script over a very large environment you often want to make sure that each machine you are trying to hit is accessible and that you have permission. Most importantly, however, you want to do this as quickly as you can. This got me to thinking. I can use the test-connection cmdlet to see if the machine is online. It’s a really good tool because it can be passed the “-quiet” parameter and is then set to return a simple true or false answer. As well, you can speed it up by passing the “-count” parameter and speed things up by only testing once.

That’s easy enough, but what is the best way to test for permissions, and quickly. I’ve given it a lot of thought, and I think the best way is to run a test-path to the admin$ or c$ shares. Using these built-in shares you can quickly verify if you have administrative privileges to the machine. Here’s the catch, if you try to run the test-path cmdlet against a machine that isn’t responding it can run very slowly. So we want to first verify that it’s on, and then test for privileges. To guarantee that the test-path wouldn’t run I had traditionally made two “IF” statements with one nested into the other.

1
2
3
4
5
If (Test-Connection $Machine -quiet -count 1){
    If (Test-Path \$Machineadmin$){
        #Code Here
    }
}

But I thought that made for some rather ugly code. Instead I wanted to put both tests into a single “IF” statement, but I was worried that both commands would run and slow everything down. Logic states that it would be rather pointless to test for the second condition of an “AND” statement if the first condition was false. So I thought that maybe PowerShell would cut out the second portion, so I write this quick script to test my theory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function A{
    write-host "Running A"
    Return $False
}
Function B{
    write-host "Running B"
    Return $True
}

If ((A) -AND (B)) {
    "Found True"
}Else{
    "Found False"
}
>>Running A
>>Found False

After playing with that code some more with various conditions I found exactly what I was hoping for. If you are testing multiple code blocks with an “AND” statement then it will execute down the path until one is false, and then stop.

So now I have shortened my test code to just one “IF” statement.

1
2
3
If ((Test-Connection $Machine -quiet -count 1) -AND (Test-Path \$Machineadmin$)){
    #Code Here
}

As far as speed goes,it will of course depend on your environment,but I found that the following times were averages in mine:

Machine doesn’t exist (No DNS): 2500 ms
Machine not online: 3800 ms
Permissions denied: 50 ms
Online and Accessible: 30 ms

 

Recently, a challenge came across my desk which included comparing very large sets of data against one another. Specifically, a list of all computers in our domain compared to a list of all computers registered with a specific application. This posed an interesting question to me, “What would be the fastest way to accomplish this?”

I set out to look for different ways of comparing lists. I can think of three. The first two would be to load all of the items into an array and then search the array, item by item, for the value using either the –match operator or the –contains operator. The third method would be to load all the items into a hash table with empty values and then search the keys to see if they exist. Now since I know that loading up a hash table should take more time than loading an array, so I want to time the entire process not just the searches.

To actually do the timing, I will use the measure-command cmdlet. If you haven’t ever used this, you should really play with it. It’s a great tool for figuring out how long any given code block takes to run. That can be useful for things like filling in a time on you write-progress applet, or reporting the time to execute back to a user. Really you can look at it as a way to avoid setting a variable to get-date and then creating a new-timespan after the command completes. It is essentially rolling it all into one.

So, it’s a race between searching Hash Tables, and Searching arrays using both –match and –contains. Here is the code I used:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 $Checks = Get-Content u:scriptworkstations.txt

$ArrayContainsTime = Measure-Command {
    $Array = @(Get-Content u:scriptworkstations.txt)
    $Found = 0
    foreach ($Name in $Checks){
        If ($Array -contains $Name){$found++}
    }
}
"Array Contains Count: t$($Array.Count)"
"Array Contains Found: t$($found)"

$ArrayMatchTime = Measure-Command {
    $Array = @(Get-Content u:scriptworkstations.txt)
    $Found = 0
    foreach ($Name in $Checks){
        If ($Array -match $Name){$found++}
    }
}
"Array Matches Count: t$($Array.Count)"
"Array Matches found: t$($found)"

$HashTime = Measure-Command {
    $HashTable = @{}

    ForEach ($Line in Get-Content u:scriptworkstations.txt){
        $HashTable.Add($Line,"1")
    }
    $Found = 0
    foreach ($Name in $Checks){
        If ($Hashtable.ContainsKey($Name)){$found++}
    }
}
"Hash Table Count: t$($HashTable.Count)"
"Hash Table Found: t$($found)"


"Milliseconds for Array Contains:t$($ArrayContainsTime.TotalMilliseconds)"
"Milliseconds for Array Matches:t$($ArrayMatchTime.TotalMilliseconds)"
"Milliseconds for Hast Table Contains:t$($HashTime.TotalMilliseconds)"

I have loaded up the text file with 2,000 entries, so we are basically comparing 2,000 items to 2,000 items. Every single one will be a match, so we can see that it’s working by making sure that the found and count values are the same. If you wanted to take this code and load two different lists, then you would see a difference there. So, without further delay, it’s off to the races!

1
2
3
4
5
6
7
8
9
Array Contains Count:   2000
Array Contains Found:   2000
Array Matches Count:    2000
Array Matches found:    2000
Hash Table Count:   2000
Hash Table Found:   2000
Milliseconds for Array Contains:    532.6136
Milliseconds for Array Matches: 9839.4498
Milliseconds for Hast Table Contains:   51.2049

So we have a winner! As you can see all the methods work, but the hash table is substantially faster than the other two, array based methods searching through all 2,000 items in under one tenth of a second! I think array using the –contains operator is still a very reasonable time, and is probably easier and more comfortable for the average scripter to use. As well, it should be said that the array with the –match operator isn’t insanely slow by any means, and is by far the most robust method for searching as it can match any portion of the name in the array. This should be used with caution, though, as it can actually create false positives. Let’s say you are looking for “Computer” in a list that contains “Computer1”. You may not expect this to be a match, but it will.

So, there you have it. If you need to search a massive list for some reason and speed is top on your mind, use a hash table!

 

A co-worker of mine posed the question of how to get the same type of pop-up that you get when you are using a simple wscript.echo in VBS. Since I am always telling him that PowerShell is typically easier than VBS to do something I was a bit annoyed, because he found one case where VB is easier.

Searching online you find that most people try to create a new object that is a VBS session and then pass the wscript.echo command over there. I don’t like that solution because it’s a bit kludgy. Instead I figured that there was a .NET object that you could use to do this, and indeed there is.

Here is a link to the MSDN Article for System.Windows.Forms.MessageBox:
System.Windows.Forms.Messagebox

Alright, so we know that there is a .NET class, so we can just make a new object, right? Well, normally yes. But there is a bit of an annoying statement on that site, “You cannot create a new instance of the MessageBox class.” So that means that I have to call the methods with a static call. Here’s the basic gist

1
2
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.MessageBox]::Show("Hello, World.")

Made with powershell and .NET object

So that is the addition of the assembly to our current shell session and then calling it via a static method. As you can see it gives a really ugly window. We can add to that, though by overloading the show method with more stuff. Specifically, let’s fill out the title, the type of buttons we’ll use and the icon it will show.

1
[System.Windows.Forms.MessageBox]::Show("This is the Text","This is the title","AbortRetryIgnore","Warning")

Alright, so how did I know what text to type to make those buttons and icon appear? What others are available to me? Well you can refer to Microsoft’s site to find out:
Message Box Buttons
Message Box Icons

So, you can see that this can be fairly powerful, but not quite as “easy” as in VBS. Now, if you care about the input that the user gives, it’s really easy to get. All you need to do is grab the output.

1
2
3
4
$Testing = [System.Windows.Forms.MessageBox]::Show("Testing Output","","AbortRetryIgnore","Warning")
#Imaginary click of the "Retry button"
$Testing
>> Retry

So you can see that we can work with the output now in other ways, but what if we want something really detailed, like we could get in VBS with the Inputbox command. Well, that’s a giant pain. I played with this for a while, but since there is no equivalent .NET object, it’s not very easy. Basically, you have to create a blank form then add all your objects to it. Set what all the buttons do and then set what happens when the form closes. I found a good tutorial written by Microsoft, so I won’t re-invent the egg:
Creating a Custom Input Box

© 2011 Ryan's PowerShell Blog Suffusion theme by Sayontan Sinha

Switch to our mobile site