Tag Archives: Set-Content

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.

Build-in Measure-Command


Notice: The following post was originally published on another website. As the post is no longer accessible, it is being republished here on tommymaynard.com. The post was originally published on April 3, 2019.


I don’t know about you, but I have like five or six open tabs in my PowerShell editor at all times. Each was used to briefly prove something worked, or that it didn’t. Occasionally, these will become articles, but much more often, they just sit there for what feels like forever. I can’t seem to close some of these tabs, or even bring myself to save the sample code. If I can’t get to it now — or this week or month — what makes me think I’ll save it off and then get back to it? I probably won’t.

But today, I’m going to close a tab. This, right after I’ve written about it here. I’m not sure if it was supposed to become an article, but it’s going to regardless.

This tab’s single function is called Get-Date. It probably sounds familiar, and it should. Here’s the idea. I want a user to be able to invoke my function and return the date, just like the Get-Date cmdlet will do for them. Now, before we go any further, it’s important to understand that using Get-Date — the function — will absolutely remove any ability to use Get-Date — the cmdlet — in any one of the other 500 different ways it can be used. That’s okay. Remember, this came out of a tab with an unknown future. This isn’t fully thought out or tested, production-ready code. It’s an artist’s sketchbook or a student’s rough draft. I didn’t even have to use Get-Date. It could have been Get-ADUser or Set-Content. As you’ll see, the date portion of this function is inconsequential. It’s just what I chose when I needed something with which to test.

In addition to returning the standard Get-Date result, I also wanted to build in a way to measure the length of time it takes for my command to complete. This was the whole experiment; this was the purpose behind the tab. To do this, requires a user include the Measure switch parameter at the time of the function’s invocation. This measurement value isn’t displayed by default, but instead, it’s written to yes, a global variable, the user can inspect if they so desire. It still produces the Get-Date cmdlet’s default output, but now there’s more output waiting in the wings if it’s wanted.

In the first example, our Get-Date function executes as though we’ve run the default, Get-Date cmdlet. We have in fact. Without the Measure parameter, all we do is run the Get-Date cmdlet. When you look over the function’s code momentarily, you’ll see the fully-qualified, Get-Date command. A fully-qualified PowerShell command includes the module name. This is a requirement because we’ve named our function the same name as the cmdlet. Command precedence order indicates that functions execute before cmdlets if both commands have the same name.

In the second example, we’ll include the Measure switch parameter, and then return the value stored in the global, $Measurement variable. What I wanted to work, did. I built in a way to run the command and measure its time to complete if I want that information. As it’s just Get-Date, it doesn’t take long at all.

PS> Get-Date
Monday, April 2, 2019 10:24:06 PM
PS>
PS> Get-Date -Measure
Monday, April 2, 2019 10:24:07 PM
PS>
PS> $Measurement
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 1
Ticks : 10231
TotalDays : 1.18414351851852E-08
TotalHours : 2.84194444444444E-07
TotalMinutes : 1.70516666666667E-05
TotalSeconds : 0.0010231
TotalMilliseconds : 1.0231

Again, if this command is run with the Measure parameter, the length of time it took to complete is written to a global variable named $Measurement. As you’d expect, that variable will maintain its value until the variable name is reused for something else in the global scope, it’s overwritten by this function, it’s specifically removed, or the current PowerShell session has ended.

As can be seen in the below function, if the Measure parameter isn’t included, we run the fully-qualified, built-in Get-Date cmdlet. If it is included, we run Measure-Command against the fully-qualified, built-in Get-Date cmdlet. We use the OutVariable common parameter twice. We use it once with the Get-Date command, placing the current date and time into $CommandResult, and once with Measure-Command, placing the time to complete in our global $Measurement variable. We display $CommandResults and leave $Measurement in memory in case, the user opts to view the measurement results.

Function Get-Date {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [switch]$Measure
    )
 
    If ($Measure) {
        Measure-Command -Expression {
            Microsoft.PowerShell.Utility\Get-Date -OutVariable CommandResult
        } -OutVariable Global:Measurement | Out-Null
        $CommandResult
    } Else {
        Microsoft.PowerShell.Utility\Get-Date
    } # If-Else.
} # End Function: Get-Date.

Before we wrap up, let’s look at a slightly modified version of the Get-Date function. This has been named Get-DateWithPause and hopefully, this will help indicate that the function is taking proper measurements. In this function, we’ve added two Start-Sleep commands where we pause for two seconds. Beneath this function, we’ll rerun our previous examples and spot those expected differences.

Function Get-DateWithPause { 
    [CmdletBinding()] 
    Param ( 
        [Parameter()] 
        [switch]$Measure 
    )
 
    If ($Measure) {
        Measure-Command -Expression { 
            Start-Sleep -Seconds 2 
            Microsoft.PowerShell.Utility\Get-Date -OutVariable CommandResult
        } -OutVariable Global:Measurement | Out-Null 
        $CommandResult 
    } Else { 
        Start-Sleep -Seconds 2 
        Microsoft.PowerShell.Utility\Get-Date 
    } # If-Else. 
} # End Function: Get-Date. 
PS> Get-DateWithPause
Monday, April 2, 2019 10:33:44 PM
PS>
PS> Get-DateWithPause -Measure
Monday, April 2, 2019 10:33:50 PM
PS>
PS> $Measurement
Days : 0
Hours : 0
Minutes : 0
Seconds : 2
Milliseconds : 2
Ticks : 20020567
TotalDays : 2.31719525462963E-05
TotalHours : 0.000556126861111111
TotalMinutes : 0.0333676116666667
TotalSeconds : 2.0020567

I’ve got a few more tabs with random pieces of PowerShell code. I’ll take a look through those, too. Just maybe I can close a few more tabs by giving their content a purpose here, even if they’re barely worthy. That said, I think this turned out alright. Someday maybe, I’ll get a measurement option builtin to all the functions I author.

Edit: In republishing this post here on tommymaynard.com, I decided to update the function a small bit. This version includes a second switch parameter (MeasureAndShow) that will both measure the command’s duration and display it at the same time, too. With more time, I might have implemented this differently, but this works.

Function Get-Date {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [switch]$Measure,
		[Parameter()]
		[switch]$MeasureAndShow
    )

    If ($Measure -or $MeasureAndShow) {
        Measure-Command -Expression {
            Microsoft.PowerShell.Utility\Get-Date -OutVariable CommandResult
        } -OutVariable Global:Measurement | Out-Null
        $CommandResult
		if ($MeasureAndShow) {
			$Global:Measurement
		}
    } Else {
        Microsoft.PowerShell.Utility\Get-Date
    } # If-Else.
} # End Function: Get-Date.
[PS7.2.1][C:\] Get-Date

Sunday, February 13, 2022 8:17:48 AM

[PS7.2.1][C:\] Get-Date -Measure

Sunday, February 13, 2022 8:17:52 AM

[PS7.2.1][C:\] $Measurement

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 2
Ticks             : 25986
TotalDays         : 3.00763888888889E-08
TotalHours        : 7.21833333333333E-07
TotalMinutes      : 4.331E-05
TotalSeconds      : 0.0025986
TotalMilliseconds : 2.5986


[PS7.2.1][C:\] Get-Date -MeasureAndShow

Sunday, February 13, 2022 8:18:01 AM

Ticks             : 1781
Days              : 0
Hours             : 0
Milliseconds      : 0
Minutes           : 0
Seconds           : 0
TotalDays         : 2.06134259259259E-09
TotalHours        : 4.94722222222222E-08
TotalMilliseconds : 0.1781
TotalMinutes      : 2.96833333333333E-06
TotalSeconds      : 0.0001781

Proving PowerShell’s Usefulness to Newbies, Part II

Back again with another example to share with the Windows PowerShell newbies. They’re out there: people that don’t know the practical power in PowerShell. The idea is to use real-world examples of things we simply wouldn’t want to do manually, in hopes that our lost friends will find the light. Maybe, that’s you.

Part I can be found here. In that post, we learned that we could use PowerShell to create 10,000 folders in about 10 seconds — that’s 1,000 folders per second, or 1 folder each millisecond.

Part II
What we’ll do today is read in the content of a file, sort the items we’ve read in, and then write our sorted results back out to the same file — it’s instant alphabetizing. Let’s start by reading in our file and see what we’re starting with.

PS> Get-Content -Path .\file.txt
web02.mydomain.com
web09.mydomain.com
dc03.mydomain.com
dc04.mydomain.com
sql08.mydomain.com
sql06.mydomain.com
dc08.mydomain.com
sql04.mydomain.com
dc10.mydomain.com
web01.mydomain.com
web03.mydomain.com
dc09.mydomain.com
web05.mydomain.com
web06.mydomain.com
dc02.mydomain.com
web07.mydomain.com
dc05.mydomain.com
web04.mydomain.com
web10.mydomain.com
sql01.mydomain.com
dc01.mydomain.com
sql03.mydomain.com
sql10.mydomain.com
web08.mydomain.com
sql05.mydomain.com
sql07.mydomain.com
dc07.mydomain.com
sql02.mydomain.com
sql09.mydomain.com
dc06.mydomain.com

Our file contains 30 fully-qualified servers names that are in no significant order. Sorting all 30 of these server names is probably going to be difficult, or at least, it’s going to be time consuming. Wrong. With one quick addition to the previous command — see the example below — our list is sorted and without any noticeable difference in the amount of time it took to complete.

PS> Get-Content -Path .\file.txt | Sort-Object
dc01.mydomain.com
dc02.mydomain.com
dc03.mydomain.com
dc04.mydomain.com
dc05.mydomain.com
dc06.mydomain.com
dc07.mydomain.com
dc08.mydomain.com
dc09.mydomain.com
dc10.mydomain.com
sql01.mydomain.com
sql02.mydomain.com
sql03.mydomain.com
sql04.mydomain.com
sql05.mydomain.com
sql06.mydomain.com
sql07.mydomain.com
sql08.mydomain.com
sql09.mydomain.com
sql10.mydomain.com
web01.mydomain.com
web02.mydomain.com
web03.mydomain.com
web04.mydomain.com
web05.mydomain.com
web06.mydomain.com
web07.mydomain.com
web08.mydomain.com
web09.mydomain.com
web10.mydomain.com

Yeah, that was tough. I ran this same command 10 times, measuring it with the Measure-Command cmdlet, so I could see the average amount of time it took to sort the list of computers. It completed this “challenge” in anywhere between 6 and 10 milliseconds. I’m sorry, but it would’ve taken me at least a few minutes do to this by hand.

Now that we know we can sort it, let’s write it back to disk. In this example, below, we’ll put our sorted results back into the file where it came from, overwriting the original contents. If you don’t want to do that, then be sure to alter the file name value for Set-Content’s -Path parameter.

PS> Get-Content -Path .\file.txt | Sort-Object | Set-Content -Path .\file.txt
PS>

Let’s verify that our file now contains the sorted contents by rerunning our first Get-Content command from the beginning of this post.

PS> Get-Content -Path .\file.txt
dc01.mydomain.com
dc02.mydomain.com
dc03.mydomain.com
dc04.mydomain.com
dc05.mydomain.com
dc06.mydomain.com
dc07.mydomain.com
dc08.mydomain.com
dc09.mydomain.com
dc10.mydomain.com
sql01.mydomain.com
sql02.mydomain.com
sql03.mydomain.com
sql04.mydomain.com
sql05.mydomain.com
sql06.mydomain.com
sql07.mydomain.com
sql08.mydomain.com
sql09.mydomain.com
sql10.mydomain.com
web01.mydomain.com
web02.mydomain.com
web03.mydomain.com
web04.mydomain.com
web05.mydomain.com
web06.mydomain.com
web07.mydomain.com
web08.mydomain.com
web09.mydomain.com
web10.mydomain.com

That’s it. We ran a one-liner command to read in some data, sort it, and push it right back to the same file. So we were able to compare, I went ahead and sorted the same, 30-line file manually. It took me 3 minutes and 30 seconds (proof below). While this wasn’t the most complex, or lengthy document one might sort, the time difference is still quite drastic. PowerShell has plenty to offer, and spending some time to learn it is going to pay off. This line of work requires all the time you have to continue to learn, and stay relevant. PowerShell can give you some time back.

PS C:\> $StartTime = Get-Date
PS C:\> $EndTime = Get-Date
PS C:\> New-TimeSpan -Start $StartTime -End $EndTime


Days              : 0
Hours             : 0
Minutes           : 3
Seconds           : 30
Milliseconds      : 860
Ticks             : 2108600828
TotalDays         : 0.00244051021759259
TotalHours        : 0.0585722452222222
TotalMinutes      : 3.51433471333333
TotalSeconds      : 210.8600828
TotalMilliseconds : 210860.0828