Tag Archives: PSCustomObject

Keeping a Continuous Total


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 July 15, 2019.


There’s a function stored in my $PROFILE (CurrentUserAllHosts). Maybe you’ve read about it before; it’s come up. Its purpose is to go out to the PowerShell Gallery and determine how many downloads I have of each PowerShell script and/or module I’ve published there. In the past, my tool provided a download total for each project along with a single total of all the downloads. Today that changes, but before it does (I seriously haven’t the updated code yet — typical), let’s show the code and results, as it currently stands.

Function Showw-PSGalleryProject {
    Param (
        [System.Array]$Projects = ('TMOutput','Start-1to100Game3.0','Get-TMVerbSynonym','SinkProfile','Show-PSDriveMenu','Switch-Prompt')
    )

    Foreach ($Project in $Projects) {
        If (Find-Module -Name $Project -ErrorAction SilentlyContinue) {
            $TempVar = Find-Module -Name $Project
        } ElseIf (Find-Script -Name $Project) {
            $TempVar = Find-Script -Name $Project
        }
        [PSCustomObject]@{
            Name = $TempVar.Name
            Version = $TempVar.Version
            Downloads = $TempVar.AdditionalMetadata.downloadCount
        }
        [int]$TotalDownloads += $TempVar.AdditionalMetadata.downloadCount
    }
    ">> Total downloads: $TotalDownloads"
} # End Function: Show-PSGalleryProject.
Show-PSGalleryProject
Name                Version Downloads
----                ------- ---------
TMOutput            1.1     1193
Start-1to100Game3.0 3.0     170
Get-TMVerbSynonym   1.4     163
SinkProfile         1.0     100
Show-PSDriveMenu    1.1     69
Switch-Prompt       1.2.0   84
>> Total downloads: 1779

See how we only have the single Total downloads above (the 1779 value)? I’ve decided I don’t like that, so it’s about to change. I want the total downloads as each of these objects are being returned (, calculated,) and written.

In the newest version of this little $PROFILE-based function — there was an earlier one — the first thing I’ve done is added the [CmdletBinding] Attribute. It’s to add the OutVariable Common Parameter. We’ll see how that’s helpful momentarily. The second thing we’ve done is begun collecting and displaying, what I’ve called the Type (find in the below code: ; $Type = Module or Script). This will differentiate when added to each object, whether one of my projects in the PowerShell Gallery is a module or script. Here’s the updated code with these two additions. Notice also, the object created inside [PSCustomObject] now includes a Type property. This is where the Module or Script determinations are included.

Function Show-PSGalleryProject {
    [CmdletBinding()]
    Param (
        [System.Array]$Projects = ('TMOutput','Start-1to100Game3.0','Get-TMVerbSynonym','SinkProfile','Show-PSDriveMenu','Switch-Prompt')
    )

    Foreach ($Project in $Projects) {
        If (Find-Module -Name $Project -ErrorAction SilentlyContinue) {
            $TempVar = Find-Module -Name $Project; $Type = 'Module'
        } ElseIf (Find-Script -Name $Project) {
            $TempVar = Find-Script -Name $Project; $Type = 'Script'
        }
        $TotalDownloads = [int]$TotalDownloads + [int]$TempVar.AdditionalMetadata.downloadCount
        [PSCustomObject]@{
            Name = $TempVar.Name
            Type = $Type
            Version = $TempVar.Version
            Downloads = $TempVar.AdditionalMetadata.downloadCount
            TotalDownloads = $TotalDownloads
        }
    } # End Foreach.
} # End Function: Show-PSGalleryProject.
Show-PSGalleryProject

The other recent addition to this code is the addition of my $TotalDownloads variable. Through each iteration, we update it so it has the current value and then displays said current value with the current object. I may have been a little too generous with my [int] casting; however, do remember that the plus sign (+) is also a concatenation operator. I want a total download count — not a string of individual download counts, strung together.

In closing, there’s something else to remember. By default (and there are ways around this), when we have more than four properties being returned per object, the “table” output becomes a “list” output — have a look.

Name           : TMOutput
Type           : Module  
Version        : 1.1     
Downloads      : 1193    
TotalDownloads : 1193    

Name           : Start-1to100Game3.0
Type           : Script
Version        : 3.0   
Downloads      : 170   
TotalDownloads : 1363  

Name           : Get-TMVerbSynonym
Type           : Script
Version        : 1.4   
Downloads      : 163   
TotalDownloads : 1526  

Name           : SinkProfile
Type           : Module
Version        : 1.0   
Downloads      : 100   
TotalDownloads : 1626  

Name           : Show-PSDriveMenu
Type           : Script
Version        : 1.1   
Downloads      : 69    
TotalDownloads : 1695  

Name           : Switch-Prompt
Type           : Script
Version        : 1.2.0 
Downloads      : 84    
TotalDownloads : 1779

While using the newly added OutVariable is helpful if I want to display the above output and capture the output into a variable at the same time, here’s how I run this now.

$PS = Show-PSGalleryProject
$PS | Format-Table -AutoSize
Name                Type   Version Downloads TotalDownloads
----                ----   ------- --------- --------------
TMOutput            Module 1.1     1193                1193
Start-1to100Game3.0 Script 3.0     170                 1363
Get-TMVerbSynonym   Script 1.4     163                 1526
SinkProfile         Module 1.0     100                 1626
Show-PSDriveMenu    Script 1.1     69                  1695
Switch-Prompt       Script 1.2.0   84                  1779

Next up is likely putting this code into a background job; it’s not the quickest thing I’ve written (although I blame that on the speed of the PowerShell Gallery lookup process, perhaps). Maybe a background job that runs in, or starts at, the end of the profile script. This, in order that these slow-to-obtain results are available sooner and with minimal impact.

Edit: 2/5/2022 –  I never did do that, although this function does still exists in my $PROFILE script. Now that I have seen this post again, perhaps there will be another push to try that. Time will tell. Oh, and here are my updated totals since this post was first written and published.

Name                Type   Version Downloads TotalDownloads
----                ----   ------- --------- --------------
TMOutput            Module 1.1     2980                2980
Start-1to100Game3.0 Script 3.0     266                 3246
Get-TMVerbSynonym   Script 1.4     293                 3539
SinkProfile         Module 1.0     302                 3841
Show-PSDriveMenu    Script 1.1     186                 4027
Switch-Prompt       Script 1.2.0   236                 4263

Invoke-RestMethod with a SOAP API

Did it again. I’m back here this evening to write another post after authoring some PoC code for a project. I’ve got to put it somewhere for later. In doing that, there’s no reason it shouldn’t be content in which others can consume.

In the past, I have absolutely written PowerShell to interact with REST (REpresentational State Transfer) APIs. What I haven’t done until now, is interact with SOAP (Simple Object Access Protocol) APIs. The below example is a full-featured PowerShell function. Its purpose, with the help of W3Schools public SOAP API, is to convert Fahrenheit temperatures to Celsius and vice versa. Typical normal stuff, except that I’m not doing the calculations. Instead, I’m sending off a temperature to an API to be converted. One of the big differences between REST and SOAP is the content of the payload. With SOAP we’re sending XML, whereas, with REST, we’d likely send JSON.

While you can inspect the code yourself, I will at minimum tell you how the function can be invoked. Its name is Convert-Temperature and it includes two parameters: Temperature and TemperatureScale. Enter an integer for the Temperature parameter and either Fahrenheit or Celsius for the TemperatureScale parameter. It’ll send off the information as XML to the API. The returned value will be extracted from the returned XML and rounded to two decimal places. I was going to make the rounding optional but I obviously changed my mind. I didn’t find any value in allowing a user to make this determination.

Here are a couple of invocation examples first, and then, the code. Maybe this will be helpful for you. I suspect it will be for me in time, as I’m going to likely have to make use of a SOAP API.

[PS7.1.0] [C:\] Convert-Temperature -Temperature 212 -TemperatureScale Fahrenheit

TemperatureScale Temperature ConvertedTemperature
---------------- ----------- --------------------
Fahrenheit               212                  100

[PS7.1.0] [C:\] Convert-Temperature -Temperature 100 -TemperatureScale Celsius

TemperatureScale Temperature ConvertedTemperature
---------------- ----------- --------------------
Celsius                  100                  212
Function Convert-Temperature {
	[CmdletBinding()]
	Param (
		[Parameter(Mandatory)]
		[int]$Temperature,

		[Parameter(Mandatory)]
		[ValidateSet('Fahrenheit','Celsius')]
		[string]$TemperatureScale
	) # End Param

	Begin {
		$Headers = @{'Content-Type' = 'text/xml'}
	} # End Begin.

	Process {
		If ($TemperatureScale -eq 'Fahrenheit') {
			$Body = @"
<soap12:Envelope xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xmlns:xsd=`"http://www.w3.org/2001/XMLSchema`" xmlns:soap12=`"http://www.w3.org/2003/05/soap-envelope`">
	<soap12:Body>
		<FahrenheitToCelsius xmlns=`"https://www.w3schools.com/xml/`">
			<Fahrenheit>$Temperature</Fahrenheit>
		</FahrenheitToCelsius>
	</soap12:Body>
</soap12:Envelope>
"@
		} Else {
			$Body = @"
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
	<soap12:Body>
		<CelsiusToFahrenheit xmlns="https://www.w3schools.com/xml/">
			<Celsius>$Temperature</Celsius>
		</CelsiusToFahrenheit>
	</soap12:Body>
</soap12:Envelope>
"@
		} # End If-Else.
	} # End Process.

	End {
		$Response = Invoke-RestMethod -Uri 'https://www.w3schools.com/xml/tempconvert.asmx' -Method 'POST' -Headers $Headers -Body $Body
		If ($TemperatureScale -eq 'Fahrenheit') {
			$ConvertedTemperature = $([System.Math]::Round($Response.Envelope.Body.FahrenheitToCelsiusResponse.FahrenheitToCelsiusResult,2))
		} Else {
			$ConvertedTemperature = ([System.Math]::Round($Response.Envelope.Body.CelsiusToFahrenheitResponse.CelsiusToFahrenheitResult,2))
		} # End If-Else.

		[PSCustomObject]@{
			TemperatureScale = $TemperatureScale
			Temperature = $Temperature
			ConvertedTemperature = $ConvertedTemperature
		}
	} # End End.
} # End Function: Convert-Temperature.

Hash Table to CSV, Revisited

I started writing about PowerShell in June 2014 at tommymaynard.com. While I’m not a Microsoft MVP, I’m certainly working to obtain Microsoft’s longevity award. While the distinction doesn’t actually exist, this year will mark six consistent years of reading, researching, spending my time with PowerShell, and giving back to the community in my written word. With the combination of my site and this one, I’ve authored somewhere close to 350+ articles, or posts.

What I’m really out to say here, is that in my time, I’ve noticed which of my writings have been consistently popular. There’s a particular post on my site that sees the largest amount of consistent visitors: It’s Hash Table to CSV. I’ve been looking for a way to take its personal success and provide more to the reader about the topic. That’s what we’re doing today with this post. I put a link to the original post on Twitter recently, and someone provided me an idea for an update — whether they realized it or not. It’s simple, but whatever. As proven, those are sometimes the most popular posts.

Before we get back into this, let’s remember how this all started. It started here: Hash Table to CSV. It’s always been a popular post, but just in case you didn’t follow this over from PowerShell.org, then you need to know this is a forward movement on that 2018 article.

I was sitting around and wondered how to best get a hash table into a CSV file. Maybe it was for configuration; I still don’t recall. Well, my option wasn’t the only option, and so today I’ll pass along something someone on Twitter pointed out that I hadn’t considered myself. We can use [PSCustomObject], introduced in (Windows) PowerShell 3.0.

I’ve long used this to create custom objects for its speed and insertion order preservation (over New-Object), but it didn’t occur to make use of it when getting a hash table into a CSV. It does work, with less work, so let’s try it on. While I won’t show the previous option, revisit the first article on this topic to see the old way; here’s the new way. We’ll start with our previously used hash table.

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

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

Once that’s populated, instead of using the hash table’s GetEnumerator method, we’ll use [PSCustomObject]. This converts each entry in our hash table into an object, and sends the object to the Export-Csv cmdlet. We get what we expect in our CSV file without the additional work scripted into the first hash table to CSV article from 2018.

[PSCustomObject]$HashTable |
    Export-Csv -NoTypeInformation -Path .\Desktop\NewHashToCsv.csv

And, that was it. Quicker, easier, and more likely to be remembered, long term.

Edit: It was pointed out to me that this didn’t produce the exact information in the CSV from 2018. Instead of having a Key and Value properties, it included a property/column for each of the hash table’s keys: Name, StreetName, and City. Honestly, I think I like this better, as it reminds me of what any command that returns objects in PowerShell would do for us. If I were to read it back into the host (via Import-Csv), my output from the operation would be more in line with what PowerShell commands produces anyway: objects with properties. Thank you for the observation, Mike!