Tag Archives: Select-Object

Explore the Command and Ensure the Efficiency

The best part of this entire process—the writing about PowerShell for coming up on nine years—has been the realization of my own mistakes while using PowerShell, and then sharing those. I have an upcoming post on this, but in between preparing and publishing that, I, yes me, made another mistake.

Let’s start with what I wrote first, which is included below. It’s a simple Get-ADComputer command. By now, we’ve all likely written plenty of these. Looks great right? Does it though?

Get-ADComputer -Filter * -Properties OperatingSystem,Description |
    Where-Object -Property OperatingSystem -like '*Server*' |
    Select-Object -Property Name, OperatingSystem |
    Sort-Object -Property OperatingSystem, Name

Before we move forward and determine what I did wrong, let’s use Measure-Command to see how long the execution takes for me on my machine.

Measure-Command -Expression {
    Get-ADComputer -Filter * -Properties OperatingSystem,Description |
        Where-Object -Property OperatingSystem -like '*Server*' |
        Select-Object -Property Name, OperatingSystem |
        Sort-Object -Property OperatingSystem, Name
}
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 4
Milliseconds      : 105
Ticks             : 41055467
TotalDays         : 4.75179016203704E-05
TotalHours        : 0.00114042963888889
TotalMinutes      : 0.0684257783333333
TotalSeconds      : 4.1055467
TotalMilliseconds : 4105.5467

Four seconds. That doesn’t seem too long, but I’m guessing it is based on the way in which the command was written. I never really trust a single test, so let’s set up the above Measure-Command command to run 10 times consecutively using ForEach-Object.

1..10 | Foreach-Object {
    Measure-Command -Expression {
        Get-ADComputer -Filter * -Properties OperatingSystem,Description |
            Where-Object -Property OperatingSystem -like '*Server*' |
            Select-Object -Property Name, OperatingSystem |
            Sort-Object -Property OperatingSystem, Name
    }
} | Select-Object -Property Seconds, Milliseconds
Seconds Milliseconds
------- ------------
      5          370
      5          213
      5          311
      5          839
      4          500
      6          234
      5           50
      5          239
      4          656
      5          421

Multiple tests and this is closer to a full five seconds per invocation. I don’t know about you, but five seconds in PowerShell is an eternity. We use PowerShell for accuracy, sure, but we use it for efficiency, as well. The quicker the better; we shouldn’t limit ourselves. If you find a mistake then fix it.

Now, let’s use the Filter parameter the way it was intended to be used. Piping to Where-Object is always going to be slower than using the filtering options provided by the commands we use. Here’s our base command corrected.

Get-ADComputer -Filter {OperatingSystem -like '*Server*'} -Properties OperatingSystem, Description |
    Select-Object -Property Name, OperatingSystem |
    Sort-Object -Property OperatingSystem, Name

And here it is wrapped up to be tested 10 times such as we did previously.

1..10 | Foreach-Object {
    Measure-Command -Expression {
        Get-ADComputer -Filter {OperatingSystem -like '*Server*'} -Properties OperatingSystem, Description |
            Select-Object -Property Name, OperatingSystem |
            Sort-Object -Property OperatingSystem, Name
    }
} | Select-Object -Property Seconds, Milliseconds
Seconds Milliseconds
------- ------------
      1          501
      0          922
      0          907
      0          895
      0          930
      0          906
      1          790
      1          534
      1          284
      0          937

Maybe you’re not worried about those lost seconds. If those aren’t that important, then let it be the possibility that someone’s going to see your failure to be efficient. If I saw this—and again I made this mistake first—I would be concerned that the person writing this code didn’t better explore the command(s) they’re using. If you’re going to use a command, then know it well enough to know how well you’re using it.

A Basic(ish) Active Directory Look-Up Script

It was just Saturday—it’s been a few weeks now, actually—that I wrote a post proclaiming to be back. Back to writing about PowerShell, that is. Why not take the first script I wrote in my new position and share it? It’s possible it has some concepts that might be helpful for readers.

The first thing I want to mention is that I hate writing scripts. Huh!? What I mean by that, is that I prefer to write functions. That didn’t happen my first time out in this position, as you’ll see, and I’m going to be okay with it. A one-off script may have a purpose. I stayed away from a function (and therefore a module [maybe], working with profile scripts [potentially], etc.). I’ll live with it.

Let’s break the script up into sections and explain what’s happening in each. Keep in mind that this isn’t perfect and there are places where I would make changes after having looked over this a couple of times. I’ll be sure to mention those toward the bottom of the post. Okay, let’s do this! I’ll put the script back together into a single, code block further below.

The beginning of the script creates two parameters: Properties and Output. Properties can accept multiple string values,—notice the []—and we’ll see that in the upcoming examples. Output can accept one string value of three predetermined values. Those are Console, GridView, and CsvFile; Console is the default parameter value.

Param (
    [Parameter()]
    [string[]]$Properties,
    [Parameter()]
    [ValidateSet('Console','GridView','CsvFile')]
    [string]$Output = 'Console'
)

Next, we create a path to a flat file called userlist.txt. This file will contain Active Directory Display Names (DisplayName). By using the $PSScriptRoot variable, all we have to do is keep our script and the text file in the same location/folder in order for it to work correctly.

Once the $Path variable is set, we attempt to run a Get-Content command against the values in the file, storing these in the $Userlist variable. If for some reason the file isn’t in place, the script will make use of the catch block of our try-catch to indicate to the user that the file can’t be located.

$Path = "$PSScriptRoot\userlist.txt"
try {
    $Userlist = Get-Content -Path $Path -ErrorAction Stop
} catch {
    Write-Error -Message "Unable to locate $Path."
}

Following that, we set our $TotalProperties variable to three Active Directory user properties we know we want. Then, if any values have been passed in using the Properties parameter, we combine those with the three properties already in the $TotalProperties variable.

$TotalProperties = @('DisplayName','EmployeeNumber','SamAccountName')
if ($Properties) {
    $TotalProperties = $TotalProperties + $Properties
}

Moving forward, we set up foreach language construct in order to loop through each user in the $Userlist variable. Remember, this variable was populated by our Get-Content command earlier. For each loop iteration, we are adding a PSCustomObject to our $Users—plural— variable. Normally, I wouldn’t store each value in a variable and instead just pump it out right there, but the script includes some output options that we’ll see next.

foreach ($User in $Userlist) {
    $Users += [PSCustomObject]@(
    Get-ADUser -Filter "DisplayName -eq '$User'" -Properties $TotalProperties |
        Select-Object -Property $TotalProperties
    )
}

Finally, we consider the output option the user either defaulted to, or didn’t. If they included the Output parameter with GridView, we pipe our $Users variable to OutGridView. If they included the Output parameter with CSVFile, we pipe out $Users variable to Export-Csv, saving them in a CSV file, and then open our saved CSV file. If they didn’t include the Output parameter, or they did with the Console value, then we display the results directly in the console. That’s it.

Switch ($Output) {
    GridView {$Users | Out-GridView -Title Users}
    CsvFile {
        $Users.GetEnumerator() |
        Export-Csv -NoTypeInformation -Path "$PSScriptRoot\$(Get-Date -Format FileDateTime -OutVariable NewFileOutput).csv"
        Invoke-Item -Path "$PSScriptRoot\$NewFileOutput.csv"
    }
    Default {$Users | Format-Table -AutoSize}
}

Although I took the base code from someone’s previously written script, this really is still much of a 1.0.0 version. Knowing that, there are some changes I might make; it’s not perfect, but it’ll get the job done.

  • While it’s not vital, I kind of wish I used a different variable name for $Path
    • It’s a path, sure, but to a specific file
    • Perhaps $FilePath, $UserFile, or $UserFilePath
      • It could’ve been more specific
  • Ensure properties passed in via the Properties parameter are valid for an Active Directory user
    • If someone sends in properties that don’t exist for an Active Directory user, it’s going to cause problems
      • Maybe check a known user, gather all the possible properties, and compare
      • (Or) Maybe wrap some error checking without having to do any property compare operation
  • Don’t use Default in the Switch language construct
    • It’s not necessary, as the Output parameter will only accept three possible values
    • Default could’ve been replaced with Console

Here are a few examples followed by the full PowerShell code in a single, code block.

I’ve redacted information from each of these images. There’s something that’s vital to know about each, however. In front of the full path (all the images but the last one), is the & operator. This is called the invocation or call operator. It informs PowerShell that everything after it should be treated as a command and that it’s not just a long, string value.

This example invokes the script without any parameters, pulling in two users from the userlist.txt file.

This example invokes the script and includes two additional Active Directory properties, which are then also included in the output.

This example does the same as the first one, however, it opens the results using the Out-GridView cmdlet.

This one opens the results using whatever program—Excel in my case—is associated with CSV files. This option is saving the file to disk, so keep that in mind, as it has no cleanup features.

This final example is included to show that it works when you’re inside the same directory as the script and text file. It also includes multiple parameters being included at the same time. You might know you can do it, but my at-work audience may not have—I’m not sure. As we’re in the directory with the script, you can actually see the inclusion of the invocation operator.

And finally, all the code in a single code block.

Param (
    [Parameter()]
    [string[]]$Properties,
    [Parameter()]
    [ValidateSet('Console','GridView','CsvFile')]
    [string]$Output = 'Console'
)

$Path = "$PSScriptRoot\userlist.txt"
try {
    $Userlist = Get-Content -Path $Path -ErrorAction Stop
} catch {
    Write-Error -Message "Unable to locate $Path."
}

$TotalProperties = @('DisplayName','EmployeeNumber','SamAccountName')
if ($Properties) {
    $TotalProperties = $TotalProperties + $Properties
}

foreach ($User in $Userlist) {
    $Users += [PSCustomObject]@(
        Get-ADUser -Filter "DisplayName -eq '$User'" -Properties $TotalProperties |
            Select-Object -Property $TotalProperties
    )
}

Switch ($Output) {
    GridView {$Users | Out-GridView -Title Users}
    CsvFile {
        $Users.GetEnumerator() |
            Export-Csv -NoTypeInformation -Path "$PSScriptRoot\$(Get-Date -Format FileDateTime -OutVariable NewFileOutput).csv"
        Invoke-Item -Path "$PSScriptRoot\$NewFileOutput.csv"
    }
    Default {$Users | Format-Table -AutoSize}
}

Apartment Hunting with PowerShell

Note: Expect a part two on this post.

I know a guy, and that guy is looking for an apartment. It turns out that apartments are going really fast and inventory is low — maybe you knew this, but it was news to me. Just about as soon as they become available, they are gone. I suggested that I might be able to lend a hand… maybe, who knows. This is not because I know someone in apartments, but rather that if there is a way to use PowerShell here, then there is a good chance I can help. He got lucky and I learned something new.

I started by going to the apartment website where he was interested and found a page that listed each apartment model and whether or not they had any availability. It was a floor plan page. I was not expecting much honestly, but I used the built-in Web Developer Tools in my browser and viewed the page source, and found some exciting news (for me and PowerShell, at least). It was enough good news that I am able to write about this whole experience.

While empty here, this data structure caught my eye. The output I had hoped to gather, was in JSON format; that was huge! Best I can tell, it is generated by a JavaScript file, which then embeds the JSON in the HTML that makes up the webpage. That is not overly important, however, but look at this structure; it is magnificent.

floorplans: [
  {...
  },
  {...
  },
  {...
  },
  {...
  },
  {...
  },
  {...
  }
],
propertyID: 60484,

Inside the floor plans JSON array ([]) are six objects, each in their own set of curly braces. Inside each of those, was a plethora of information regarding each floor plan. These properties included things like Model, Sq.Ft., Beds, Baths, etc. Let’s start by taking a look at the Watch-Apartment PowerShell function I wrote. Just a note, but in order to make this work for yourself, you will need to edit the path in the $ContentPath variable.

function Watch-Apartment {
    $Uri = 'https://theplaceatcreekside.securecafe.com/onlineleasing/the-place-at-creekside/floorplans'
    $WebRequestContent = (Invoke-WebRequest -Uri $Uri).Content
    $ContentPath = 'C:\users\tommymaynard\Documents\tommymaynard.com\Apartment Hunting\WebpageContents.txt'
    Set-Content -Path $ContentPath -Value $WebRequestContent

    $File = Get-Content -Path $ContentPath
    $Pattern = "floorplans:(.*?)propertyID:"
    $ParsedPage = [regex]::Match($File,$Pattern).Groups[1].Value
    $ParsedPage = $ParsedPage.Trim(); $ParsedPage = $ParsedPage.TrimEnd(',')

    $JsonDocument = ConvertFrom-Json -InputObject $ParsedPage
    $JsonDocument |
        Select-Object -Property @{Name='Available';Expression={if ($_.isFullyOccupied -eq 0) {"Yes ($($_.availableCount))"} else {'No'}}},
        @{Name='Model';Expression={$_.name}},
        @{Name='Sq.Ft.';Expression={$_.sqft}},
        @{Name='Beds';Expression={$_.beds}},
        @{Name='Baths';Expression={$_.baths}} |
    Format-Table -AutoSize
}
Watch-Apartment

We will discuss the above function using its line numbers:

Line 1: Declares/creates the Watch-Apartment function.
Line 2: Stores the site’s URI inside the $Uri variable.
Line 3: Invokes an Invoke-WebRequest command using the URI and stores the Contents (as in the Contents property) inside the $WebRequestContent variable.
Line 4: Creates the ContentPath variable to hold a path to a text file that will be created in the next line/command.
Line 5: Takes the content from the webpage and writes it to a text file.

Writing to a file was not a requirement, however, it was my first choice for whatever reason and so I went with it, and then stayed with it.

Line 7: Read in the contents from the file and store them in the $File variable.
Line 8: Create a Regex pattern to allow us to collect all the content between the word “floorplans:” and “propertyID:”.
Line 9: Parse out the data we want and store it in the $ParsedPage variable.
Line 10: Trim off the white space from the beginning and end of the JSON string, and then trim off the trailing comma at the end of the JSON string.

Line 12: Assign the $JsonDocument variable the value assigned to the $ParsedPage variable after it has been converted from JSON by CovertFrom-Json.
Lines 13 – 19: Use Select-Object to select and modify our desired properties.

In the final command, we determine which floor plan is available, how many apartments there are, which model it is, how many square feet that model has, and how many bedrooms and bathrooms it has. Each line/property includes a calculated property and often, just to modify the case of the text.

I edited the friend’s PowerShell profile script and added this function. Not only is the function added to the PowerShell session by the profile script, but it also invokes the function, too. Open PowerShell, and just about instantly know whether anything is available or not.

These were the results the first time it ran back on my machine.

It was a good thing that the Available property included both the isFullyOccupied (“Yes” versus “No”) and the availableCount (# of apartments) information. Take a look at the next image to see why.

In the above image, it still says, “Yes,” but the count is zero. Apparently, my decision to include both values was the right choice, as this information is not all updated at the same time.

Later that same day, it cleared up.

Now, he waits, as my work is done.

Note: As stated at the top of this post, expect a part two. There is more than one apartment complex now.

AWS PowerShell Command Count

Back in the day, I was astonished by the number of PowerShell commands that AWS (Amazon Web Services) had written. It was a huge number—I believe it was around 5,000. There’s probably a post or two on this site where it’s mentioned. Based on the number, it was clear that AWS had made a commitment to (wrapping their APIs with) PowerShell. Back then, it was easy to calculate the number of commands because of that single, PowerShell module. Install the module, count the commands (programmatically of course), and done. Over the last two or three years—I’m not 100% sure—-they moved to a model where modules are separated out by service. I greatly suspect that the single-module model was no longer suitable. Who wants to import a few thousand commands by default, when they only needed maybe one or two? Over time, I suspect that it probably wasn’t the most efficient way in which to handle the high number of commands.

Now, AWS has modules with names that begin with AWS.Tools. In the below example, I have assigned the $AWSModules variable with all the AWS modules located in the PowerShell Gallery that begin with the name AWS.Tools.*.

$AWSModules = Find-Module -Name AWS.Tools.*
$AWSModules.Count
241
$AWSModules | Select-Object -First 10 -Property Name,Version
Name                              Version
----                              -------
AWS.Tools.Common                  4.1.10.0
AWS.Tools.EC2                     4.1.10.0
AWS.Tools.S3                      4.1.10.0
AWS.Tools.Installer               1.0.2.0
AWS.Tools.SimpleSystemsManagement 4.1.10.0
AWS.Tools.SecretsManager          4.1.10.0
AWS.Tools.SecurityToken           4.1.10.0
AWS.Tools.IdentityManagement      4.1.10.0
AWS.Tools.Organizations           4.1.10.0
AWS.Tools.CloudFormation          4.1.10.0

Once I determined that there were over 240 individual modules, I piped the $AWSModule variable to the Get-Member command to view its methods and properties. I didn’t know what I was after, but out of the returned properties, I was intrigued by the Includes property. I used that property as a part of the below command. Each of the 241 entries includes a Command, DscResource, Cmdlet, Workflow, RoleCapability, and Function nested property inside the Includes property.

$AWSModules.Includes | Select-Object -First 3
Name Value
---- -----
Command {Clear-AWSHistory, Set-AWSHistoryConfiguration, Initialize-AWSDefaultConfiguration, Clear-AWSDefaultConfiguration…}
DscResource {}
Cmdlet {Clear-AWSHistory, Set-AWSHistoryConfiguration, Initialize-AWSDefaultConfiguration, Clear-AWSDefaultConfiguration…}
Workflow {}
RoleCapability {}
Function {}
Command {Add-EC2CapacityReservation, Add-EC2ClassicLinkVpc, Add-EC2InternetGateway, Add-EC2NetworkInterface…}
DscResource {}
Cmdlet {Add-EC2CapacityReservation, Add-EC2ClassicLinkVpc, Add-EC2InternetGateway, Add-EC2NetworkInterface…}
Workflow {}
RoleCapability {}
Function {}
Command {Add-S3PublicAccessBlock, Copy-S3Object, Get-S3ACL, Get-S3Bucket…}
DscResource {}
Cmdlet {Add-S3PublicAccessBlock, Copy-S3Object, Get-S3ACL, Get-S3Bucket…}
Workflow {}
RoleCapability {}
Function {}

After some more inspection, I decided that each command populated the Command property and the Cmdlet or the Function property, as well, depending on what type of commands were included in the module. Next, I just returned the commands using dot notation. This isn’t all of them, but you get the idea.

$AWSModules.Includes.Command
Clear-AWSHistory
Set-AWSHistoryConfiguration
Initialize-AWSDefaultConfiguration
Clear-AWSDefaultConfiguration
Get-AWSPowerShellVersion
...
Remove-FISExperimentTemplate
Remove-FISResourceTag
Start-FISExperiment
Stop-FISExperiment
Update-FISExperimentTemplate

If you’re curious, as I was, Update-FISExperimentTemplate, “Calls the AWS Fault Injection Simulator UpdateExperimentTemplate API operation.” I have no idea.

With a little more dot notation, I was able to get the count.

$AWSModules.Includes.Command.Count
8942

And now, I know what I used to know. I also know that AWS has been busy. And, that they’ve continued to make a huge effort in the PowerShell space. If you don’t go straight to the PowerShell Gallery, and I would recommend you do, you can always start at the AWS PowerShell webpage.

Changes to Invoke-RestMethod in PowerShell 7

The Invoke-RestMethod cmdlet has been with us since PowerShell 3.0. For those of us paying attention to PowerShell during the 2.0 to 3.0 transition, it was a huge release and the inclusion of this new command was only one of the reasons why. From that point forward, we had a built-in, PowerShell way to interact with RESTful Web Services. REST (Representational State Transfer) web services accept HTTP(S) requests, and respond with structured data, often in the form of JSON. This structured data is converted by the Invoke-RestMethod cmdlet into PowerShell objects. The command was greatly improved in PowerShell 6, but today we’re going to focus on the newest changes in PowerShell 7.

If you spent any time in the help file/online docs for Invoke-RestMethod (5.1 and 7.0), then you’ve likely seen the below example. It shows the basic ability of the cmdlet, and returns some worth wild results, too. The below results are created for us after issuing a specific, Invoke-RestMethod command. You can see it below. We first assigned the $Uri variable an exact, URI (Uniform Resource Identifier) value. This complete URI is the combination of the base URI, “https://blogs.msdn.microsoft.com,” and the endpoint, “/powershell/feed.” We then used the variable in the Invoke-RestMethod command, which makes an HTTPS request for the data, receives a response, and finally, converts it into PowerShell objects, where it’s then filtered by our Select-Object command.

$Uri = 'https://blogs.msdn.microsoft.com/powershell/feed/'
Invoke-RestMethod -Uri $Uri | Select-Object -Property Title,pubDate
 
title                                                     pubDate
-----                                                     -------
Secrets Management Module Vault Extensions                Thu, 06 Feb 2020 22:59:33 +0000
Public Preview of PowerShell Support in Jupyter Notebooks Thu, 06 Feb 2020 21:46:52 +0000
Secrets Management Development Release                    Thu, 06 Feb 2020 20:40:50 +0000
Announcing the PowerShell 7.0 Release Candidate           Tue, 17 Dec 2019 00:42:17 +0000
Improvements in Windows PowerShell Container Images       Tue, 10 Dec 2019 00:32:57 +0000
PowerShell 7 Preview 6                                    Fri, 22 Nov 2019 01:20:37 +0000
PowerShell Extension Roadmap                              Mon, 04 Nov 2019 13:19:35 +0000
DSC Resource Kit Release October 2019                     Wed, 30 Oct 2019 20:52:48 +0000
PowerShell 7 Preview 5                                    Wed, 23 Oct 2019 19:11:44 +0000
DSC Resource Kit Release September 2019                   Thu, 19 Sep 2019 18:07:12 +0000

As we continue, do keep in mind that Invoke-RestMethod can do much more than what we’ve seen so far, and what we’ll cover. It can do POST requests, handle pagination, handle multipart/form-data, and it can pass in multiple headers if required. Don’t forget about authentication and credentials; it can handle those, as well.

In PowerShell 7, there are a couple of newer features we’ll cover today. Before we do that, if you didn’t use this cmdlet in PowerShell 6, then do know that there were many changes in that version. You can actually find the PowerShell 6 features in the Invoke-RestMethod PowerShell 7 documentation.

Before we get into the new features, here’s another example of using Invoke-RestMethod. This article has coincided well with some of my AWS research and testing with Lambda and API Gateway. The below image shows some testing against a random number generator that has a REST API in front of it. That’s why there are various numbers in the results.

There are two new parameters for Invoke-RestMethod in PowerShell 7: StatusCodeVariable and SkipHttpErrorCheck.

The StatusCodeVariable parameter allows you to assign the status code, returned by the request to the RESTful web service, to a variable. As the below example was successful, it assigned 200, or an OK successful status response code, to the scv variable.

As mentioned, the other new parameter addition to this cmdlet is SkipHttpErrorCheck. This parameter causes Invoke-RestMethod to ignore an error status. It treats them as though they were successful requests. Without it, however, we received the error, as can be seen in the first Invoke-RestMethod invocation below. It’s not clear by the image, but the value in $Uri has been changed between our successful examples and this one.

The second invocation in the below image appeared that it was (somewhat) successful, but that was thanks to SkipHttpErrorCheck. It wasn’t, and you can gather that from the message that was returned, even though we’ve skipped the error.

The question is, “how might you know for sure if there was an error and you skipped it?” Yep. The StatusCodeVariable parameter and the variable to which you’ve assigned the status code. These two new parameters can be used in conjunction. The final command in the below example, uses both of the new PowerShell 7 Invoke-RestMethod parameters, as part of a single command.

Invoke-RestMethod is a complex command that consists of many ways in which it can be used. A full discussion on everything you can do with Invoke-RestMethod could fill up an entire PSBlogWeek (or two), all by itself. Even so, with the release of PowerShell 7, it made sense that as a group, we helped highlight this command and its two new parameters. It’s just a small offering of all the new additions in the newest version of PowerShell.

Welcome to PowerShell 7.0.

Hash Table to CSV

Update: There’s a “part two” now. See the link at the bottom of this post.

I was sitting here, and I wondered, how do I get a hash table into a CSV? Have I even ever done that? Now, a few commands in, and I can’t even remember why I wondered that. There was a reason, I just wish I could remember what it was. Whatever it was, I should be more likely to remember how to get a hash table into CSV when I need it after today’s post. Was it on-disk storage for a hash table for some software configuration? Ugh, what was it?

Anyway, let’s start by assigning a hash table to a variable. Maybe it’ll come back to me.

$HashTable = @{
    Name = 'Tommy'
    StreetName = 'Vista Pl.'
    City = 'Tucson'
}

$HashTable
Name                           Value
----                           ----- 
Name                           Tommy
StreetName                     Vista Pl.
City                           Tucson

Now that we’ve assigned our $HashTable variable the value of our hash table, we can try and get it into a CSV. At first, someone might try the below option. Let’s see how that works.

$HashTable |
    Export-Csv -NoTypeInformation -Path .\Desktop\HashToCsv.csv

As you can see, this doesn’t work.

In order to get this to work properly, we need to use the GetEnumerator method. I seem to use this quite often. This allows us to walk through each key-pair in our hash table.

$HashTable.GetEnumerator() |
    Export-Csv -NoTypeInformation -Path .\Desktop\HashToCsv2.csv

Now it’s just perfect, minus the whole Name property (column). Huh? I only expected the Keys and Values, like we’d see produced onscreen. With this in mind, let’s instead pipe to the Select-Object cmdlet before Export-Csv and get things properly filtered.

Update: It dawned on me, after I made all these screen captures, that I wasn’t expecting to see the above Name property included. Sure, it holds the same values as Name, but in a host program, we’re actually accustomed to seeing Name and Value, not Key and Value.

$HashTable.GetEnumerator() |
    Select-Object -Property Key,Value |
        Export-Csv -NoTypeInformation -Path .\Desktop\HashToCsv3.csv

My next logical thought was, can we use calculated properties? Can I use a different descriptor than Key and Value? You bet we can — take a look.

$HashTable.GetEnumerator() |
    Select-Object -Property @{N='Property';E={$_.Key}},
    @{N='PropValue';E={$_.Value}} |
        Export-Csv -NoTypeInformation -Path .\Desktop\HashToCsv4.csv

So yeah, there we go. I can use a hash table, saved to disk, for … whatever reason. I still don’t remember why I wondered this originally, but in case it’s helpful, I know where to find this when I remember why I wondered.

Update: There’s a follow-up post now that includes an easier way.

A Variable’s Current and Previous Value

How to Store a Variable’s Previous Value with the New Value

There was a recent post on the PowerShell Facebook group that said this: “Is there a function or option to check previous $Global:variable -ne Current $Global:variable?” The person that created the post wanted to know if there’s a way to recall the last value a variable stored, once it had already been assigned something new. Here’s my answer, and potential solution.

You have to think about a variable’s previous assignment, as if it never existed, even though you know that variable held something, or was assigned something, previously. Once its value is gone, it’s gone. Now, all that said, I came up with an option. The option, is to use a variable’s description property to store its previous value.

Let’s begin by checking the value of an uninitialized variable, and then assign it something.

PS > $x
PS > # No value.
PS > $x = 'first value'
PS > $x
first value

Now that our variable has a value, let’s take a look at all the variable’s properties using the Get-Variable cmdlet. Notice the Description property; we’re going to use this in a less than conventional way.

PS > Get-Variable -Name x | Select-Object -Property *

Name        : x
Description :
Value       : first value
Visibility  : Public
Module      :
ModuleName  :
Options     : None
Attributes  : {}

Using the Set-Variable cmdlet, we’ll make two modifications to our variable, at nearly the same time. First, we’ll update the Description property, so that it’s holding the original variable assignment (the string ‘first value’). In the same, Set-Variable command, we’ll modify the current value of the variable to the string ‘second value.’ Notice in the final below command, that our Description property has a value now, too.

PS > Set-Variable -Name x -Value 'second value' -Description $x
PS > $x
second value
PS > Get-Variable -Name x | Select-Object -Property *

Name        : x
Description : first value
Value       : second value
Visibility  : Public
Module      :
ModuleName  :
Options     : None
Attributes  : {}

In the next example, you’ll see how we can return the original value and the variable’s updated value. Beneath that, I’ve included a couple ways to compare these values. That gets back to what the person on Facebook was trying to determine. Again, this is fairly unconventional use of the Description property, but it does avoid the need for a second variable to hold the first variable’s original value. That said, one of my examples uses a second, comparison variable.

PS > (Get-Variable -Name x).Description
first value
PS > (Get-Variable -Name x).Value
second value
PS > $x
second value

PS > (Get-Variable -Name x).Description -ne (Get-Variable -Name x).Value
True
PS > (Get-Variable -Name x).Description -ne $x
True
PS > $y = (Get-Variable -Name x).Description
PS > $y -ne $x
True

In these last examples, we’re running into a bit of a problem, we are going to have to keep in mind (if anyone even dares use this approach). When we take a value that isn’t a string, and place it into the Description property, it becomes a string. That means, that when we take it back out, we’ll need to cast it back to its proper type. Take a look at the next series of examples for some assistance with this concept.

PS > $z = 5
PS > Set-Variable -Name z -Value 10 -Description $z
PS > Get-Variable -Name z | Select-Object -Property *

Name        : z
Description : 5
Value       : 10
Visibility  : Public
Module      :
ModuleName  :
Options     : None
Attributes  : {}

PS > (Get-Variable -Name z).Description | Get-Member |
>>> Select-Object -Property TypeName -Unique

TypeName
--------
System.String

PS > [int](Get-Variable -Name z).Description
5
PS > [int](Get-Variable -Name z).Description | Get-Member |
>>> Select-Object -Property TypeName -Unique

TypeName
--------
System.Int32

This goes for decimals values, too. If you use the Description property to store something that’s not a string, then you’re going to have to properly cast it when you’re taking it out, or you’re going to have a string. In these next examples, I used the GetType method to determine the value’s type, as opposed to Get-Member and Select-Object used above.

PS > $m = 2.5
PS > $m.GetType().Name
Double
PS > Set-Variable -Name m -Value 3.5 -Description $m
PS > $m
3.5
PS > $m.GetType().Name
Double
PS > (Get-Variable -Name m).Description
2.5
PS > (Get-Variable -Name m).Description.GetType().Name
String
PS > [double](Get-Variable -Name m).Description
2.5
PS > ([double](Get-Variable -Name m).Description).GetType().Name
Double

And that’s it. It’s may not be the first choice for saving a previous variable’s value, but it’s a choice. I rather liked the array option that was recommended; however, like the $Error array, I’d be tempted to put the newest/current value of a variable into index 0 and not at the end of the array. Anyway, back to real life now.

View Current PowerShell.org Q&A Forum Topics

Note: Update added at the bottom on this post on August, 9, 2016. Please read.

Sometimes you don’t always have the time to finish something you’ve started. For me, it was this function. I pounded this out in a quick few minutes, and while I don’t see myself investing in it any further, I didn’t want to forget the function, and thought I would hang on to it somewhere. Well, that’s why it’s here, especially as someone may find it useful, or helpful.

The function, which I called Get-PowerShell.orgForumTopic, runs out to PowerShell.org and grabs the current topics (page one) from the PowerShell Q&A forum (http://powershell.org/wp/forums/forum/windows-powershell-qa). It only returns the Thread name and the URL, because, well, that’s what seemed useful and relevant at the time I wrote it (which was many months ago).

Function Get-PowerShell.orgForumTopic {
    [CmdletBinding()]
    Param ()

    Begin {
    } # End Begin.

    Process {
        (Invoke-WebRequest -Uri 'http://powershell.org/wp/forums/forum/windows-powershell-qa/' |
            Select-Object -ExpandProperty Links |
            Where-Object {$_.outerHTML -like '*http://powershell.org/wp/forums/topic*'} |
            Select-Object @{N='Thread';E={$_.innerHTML}},@{N='Url';E={$_.href}} |
            Select-Object -First 30)[(0..30 |
                ForEach-Object {
                    If (-not($_ % 2)) {
                        $_
                    }
                 }
            )]
    } # End Process.

    End {
    } # End End.
} # End Function: Get-PowerShell.orgForumTopic

Here’s what the results looked like in the ConsoleHost, near in time to when this post was published.

current-powershell.org-qa-forum-topics-01

While I never added any more to this function, I had some ideas: add the thread status, add the “started by user,” add the user that made the last post, add the number of posts per topic, and allow it to run against other PowerShell.org forum topics. It might’ve also been helpful to include additional pages, if requested by the user of the function, such as adding a -Pages parameter (-Pages 4).

Anyway, here it is. Beside being helpful to see the top PowerShell.org Q&A forum posts at a specific point in time, it’s an interesting example of reading from a webpage, of which I had minimal experience. So yeah, I probably learned something by doing this exercise.

If you want to do the same, then start by running the first command, Invoke-WebRequest -Uri ‘http://powershell.org/wp/forums/forum/windows-powershell-qa/’. Then add the pipe and first Select-Object command and run that. Then add the Where-Object command, and so on. This will allow you to see how I finally got to only returning the “Threads” and “Urls.” Take care.

Update: Since a site redesign at PowerShell.org, this function, no longer functions. I’m not sure that I’ll bother to update it — I don’t get the feeling that it was ever used by anyone — but I’ll keep this post up for anything else it may offer, that may be helpful in learning PowerShell.

Add, Concatenate, and a Little More

My desire to know Windows PowerShell from beginning to end isn’t just for me. It’s also for those around me — online, and in real life. I want to be a go-to-guy for all things PowerShell. I’ve found something I really love about the industry right now, and so I want know it well, and share it often. I truly believe that learning the fundamentals of PowerShell make one much more prepared to use the language interactively, as well as inside scripts and functions. I’ve said it before: It’s easy to spot someone that doesn’t know the fundamentals of PowerShell, and is only out to learn as much as is required to complete their current task.

A couple weeks ago a coworker asked me why their conditional wasn’t working (think, the part inside the parentheses in an If statement, for example). While I never saw the code, as the conversation happen over cube walls and by instant message, I suspected they were trying to compare a numeric value with something that wasn’t numeric. It turned out I was right. I determined this, the moment they said they had quotes around their numeric value.

It’s simple. The moment we put numeric values inside quotes — single or otherwise — we are no longer working numbers — we’re working with strings. Take a look at the example below. In the first command, we use addition to add the numeric values 5 and 10, which results in the (numeric) value of 15. In the second command, we concatenate (think, combine or join) — using the same operator (+). When we concatenate the string value of 5 with the string value of 10, we create the (string) value of 510.

PS> 5 + 10
15
PS> '5' + '10'
510

The information I sent to my colleague by IM is listed below. I thought it would best explain the difference between a “numeric value” inside quotes, and one not inside quotes. In this example, 123 is an integer (System.Int32) without quotes around it, and a string (System.String) with quotes around it. A quick note about Select-Object’s -Unique switch parameter: This parameter returns duplicated results once, and only once. Had this not been included, it would have returned the same result for each member of the object (each method, property, etc.).

PS> 123 | Get-Member | Select-Object TypeName -Unique

TypeName
--------
System.Int32

PS> '123' | Get-Member | Select-Object TypeName -Unique

TypeName
--------
System.String

In our first example in this post, we saw what happens when the add two numeric values and what happens when we concatenate two strings. If you think like me, you’re probably wondering what happens when we concatenate a string value to a numeric value, and add a numeric value to a string character. It depends. Take a look at the slightly, modified example below, and then we’ll discuss it.

PS> 5 + '10'
15
PS> '5' + 10
510

Did you figure it out what’s happened in those examples? In the first command, 5 + ’10’, the numeric on the left of the equation, has forced the string on the right side, to switch from a string to a numeric data type. With two numeric values, it then added the two values and returned 15.

The second command, or equation, works much like the first one, except in reverse. The string character 5 on the left, forces the numeric value on the right into being a string and then concatenates the two, which results in 510 again. Whatever is on the left is going to try and change the type of whatever is on the right — when it can. So, when can’t it?

PS> 5 + '1a2b3c'
Cannot convert value "1a2b3c" to type "System.Int32". Error: "Input string was not in a correct format."
...
PS> '1a2b3c' + 10
1a2b3c10

The first command in the example above fails because it cannot convert the string, ‘1a2b3c,’ into a numeric value because it includes non-numeric characters — makes sense to me. Notice that this error helps prove what I said earlier: The data type of value on the left is being used to try and change the data type of the value on the right. The second equation works, because again, we’re simply joining, or concatenating, two values.

PS> 'Have a great ' + 2016 + '!'
Have a great 2016!

In closing, I’d like to add a word about the concatenation operation just above. While the plus sign, or rather, the concatenation operator does what it does well, it’s not used nearly as much as it was in the past for building strings — at least, I don’t see it much anymore. In fact, when I do see it, I almost always assume that what I’m looking at was written long ago and then borrowed. What you’re more likely to see these days, is in the example below.

PS> $Year = 2016 # It's a numeric value (no quotes).
PS> # Use double-quotes (below) to display the variable's value inside the string.
PS> "Have a great $Year!" # No concatenation operators needed!
Have a great 2016!

Thanks for the reading my first post of 2016!

Prep computers.txt File for HTA

Last week, I opted to share a couple of old HTAs I had written. HTAs are HTML Applications and allow administrations the ability to create a graphical interface for their scripts. It’s an older technology and not something I still write, or use. Even so, I wanted to share them with the community. As they’re not Windows PowerShell-related, I thought I should circle back on that post and incorporate some PowerShell.

The second of the two HTAs I shared was called Remote Desktop Assistant (download link: RDAssistantv2.1 (9309 downloads ) ). Its purpose is to allow a user to select a computer description from a list and open Remote Desktop to connect to that computer. I know, I know, this goes against all things PowerShell, but it was written a long time ago. The HTA has a requirement for an external text file called computers.txt that stores computer descriptions and computer names / IP addresses, such as we have in the list below. It’s a computer description, a semi-colon, and the computer name or IP address.

computer1;10.10.10.5
computer2;dns1.mydomain.com
computer3;10.10.10.9

We can make use of Active Directory and PowerShell to create this list and subsequent text file, so we don’t have to do it manually. In fact, once you had your code written you could schedule it to ensure the computers.txt file was as accurate as the last time the scheduled task ran. While the preferred way to do this would be to store host names (at least in my opinion), I’ll show examples of collecting the IP addresses, too.

In the first example, we’ll pull all of our servers from a single Organizational Unit to return the names and DNSHostNames.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties DNSHostName | Select-Object Name,DNSHostName

Name                                                        DNSHostName
----                                                        -----------
SQL01                                                       SQL01.mydomain.com
SQL02                                                       SQL02.mydomain.com
SQL03                                                       SQL03.mydomain.com
SQL04                                                       SQL04.mydomain.com

In this example, we return the names and IP addresses.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties IPv4Address | Select-Object Name,IPv4Address

Name                                                        IPv4Address
----                                                        -----------
SQL01                                                       10.10.10.30
SQL02                                                       10.10.10.31
SQL03                                                       10.10.10.32
SQL04                                                       10.10.10.33

While these are the results we want, we need to get them into the proper format. We’ll do this by looping through each result and concatenating the two properties with semi-colon in between. To do this, we do not need to use the Select-Object cmdlet to return the Name and IPv4Address, or DNSHostName.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties IPv4Address | ForEach-Object {"$($_.Name):$($_.IPv4Address)"}
SQL01;10.10.10.30
SQL02;10.10.10.31
SQL03;10.10.10.32
SQL04;10.10.10.33

With the host name set, we’ll take this one step further and create our computers.txt file.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties DNSHostName | ForEach-Object {"$($_.Name):$($_.DNSHostName)"} | Out-File -FilePath C:\computers.txt
PS> Get-Content -Path C:\computers.txt
SQL01;SQL01.mydomain.com
SQL02;SQL02.mydomain.com
SQL03;SQL03.mydomain.com
SQL04;SQL04.mydomain.com

Chances are good that if you use the DNSHostName, you’re never going to have an Active Directory computer object returned without one. The same can’t be said if you use the IPv4Address, as this property is created at the time the results are returned (it queries DNS). Think about it, have you ever seen an IPv4Address property inside Active Directory Users and Computer when viewing a computer object? The DNSHostName option might be the better option, but I’ll leave that up to you.