Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
November 29, 2024

November 2024 PowerShell Potluck

I'm back with another PowerShell Potluck! This month, I have a variety of topics to share with you. I have a new module for working with Bluesky, a new function for encrypting environment variables, a custom history formatting function, and more. I also have a scripting challenge for you to try out. Let's dig in!

PSBluesky

For all practical purposes, I have abandoned X, as many others have. I have moved my primary social media presence to Bluesky. One of the things I like about this platform is the open and public API. Bluesky is built on top of the AT Protocol and is well documented. I have created a new PowerShell module called PSBluesky that you can use to interact with Bluesky directly from a PowerShell prompt. The module requires PowerShell 7 and can be installed from the PowerShell Gallery.

Install-Module PSBluesky

Before you can use the module, you need to create a Bluesky session with a credential. I'm recommended people use an app password. You can look at the README file in the module's GitHub repository for more information, or if you have installed the module run Open-BskyHelp to open a PDF version of the file.

Run Get-BskyModuleInfo to see a list of available module commands and aliases.

PS C:\> Get-BskyModuleInfo

   Module: PSBluesky [v2.1.0]

Name                 Alias               Synopsis
----                 -----               --------
Add-BskyImage                            Upload an image to Bluesky
Find-BskyUser        bsu                 Search for Bluesky user accounts
Get-BskyAccountDID                       Resolve a Bluesky account name to its DID
Get-BskyFeed         bsfeed              Get your Bluesky feed
Get-BskyFollowers    bsfollower          Get your Bluesky followers
Get-BskyFollowing    bsfollow            Get a list of Bluesky accounts that you follow
Get-BskyModuleInfo                       Get a summary of the PSBlueSky module.
Get-BskyNotification bsn                 Get Bluesky notifications.
Get-BskyProfile      bsp                 Get a Bluesky profile
Get-BskySession      bss                 Show your current Bluesky session.
Get-BskyTimeline     bst                 Get your Bluesky timeline
New-BskyPost         skeet               Create a Bluesky post
Open-BskyHelp        bshelp              Open the PSBluesky help document
Start-BSkySession                        Start a new Bluesky session
Update-BskySession   Refresh-BskySession Refresh the Bluesky session token

Once you've run Start-BskySession, you can use other module commands.

A Bluesky profile
figure 1

The module uses custom formatting and offers features like clickable links.

A Bluesky timeline
figure 2

You can also post a message right from a PowerShell prompt, complete with tags, mentions, and links.

Skeet "I'm writing about the PSBluesky module in the monthly newsletter wrapup. #PowerShell" -Verbose

The module includes custom verbose messaging.

Custom verbose messaging
figure 3

I'm having fun with this module and have even more features planned. Even if you are not a Bluesky user, you might want to look at the code to see how I am building tools around a REST API.

New-EncryptedEnvironmentVariable

Here's an item that came across my Bluesky feed from Stephen Valdinger, more commonly known as @steviecoaster in PowerShell circles. He has posted a function that lets you create an encrypted environment variable.

The function uses the native .NET data protection APIs to create a variable with a secure string value. When you create the variable, you also need to specify the environment scope:

New-EncryptedEnvironmentVariable -Name zSecret -Value BananaMonkey246 -Scope machine

This will create a persistent environment variable in the machine scope. I created this in a PowerShell 7 session.

PS C:\> $env:zSecret
System.Security.SecureString

Because this is an environment variable, I can access it in a different PowerShell session, such as this Windows PowerShell session.

PS C:\> $env:zSecret
01000000d08c9ddf0115d1118c7a00c04fc297eb010000000728496af3d3f946bfe05138d1391c1e00000000020000000000106600000001000020000000be5e6034f844605dfed25dbec739971f6f0ca5433e4d6e79edf56d4b1f728724000000000e8000000002000020000000eb41e9c1bb3f27e28f265935db9b1d23d98778ed75cbd049a79fcab63c9a642a2000000068689c12c24ccf403b1cd1804b6c7d20595238ad447842dc16daaf13d74532ba400000008afe8788627110465842706eb7ed4ba4fb21fc9c93efb35ca3c5fd8f35467c5dc107d389f82e6058e568fe3bc2acb66528429fabead747fce1e73739764dbc6c
PS C:\>

Here I get the secure string value. To use it, I can convert it back to a secure string:

$ss = $env:zSecret | ConvertTo-SecureString

I'll can verify the value by creating a credential object with the secure string and then resolving it with the GetNetworkCredential() method.

PS C:\> $cred = [PSCredential]::new("foo",$ss)
PS C:\> $cred.getNetworkCredential().Password
BananaMonkey246

This kind of persistence only works if you specify User or Machine as the scope. Any Process scoped variables will be lost when the session ends. Remember, the secure string can only be decrypted on the same machine and user account that created it.

If you find a use for this function, I'd love to hear about it.

History Custom Formatting

I use the Get-History cmdlet all the time. One thing I like in PowerShell 7 is that it shows you how long the command took to complete.

PS C:\> Get-History -count 3

  Id     Duration CommandLine
  --     -------- -----------
 155     1:02.276 $l = get-Winevent -ListLog * -ComputerName thinkx1-jh
 156        0.003 get-history 1
 157        0.030 get-history

But I wanted to see a little more information, so I wrote a custom formatting file using New-PSFormatXML. I have shared the file as a gist on GitHub. The file uses $PSStyle so it requires PowerShell 7. After you've downloaded the file, import it into your session.

Update-FormatData -AppendPath c:\scripts\history.format.ps1xml

This gives you a new table view.

Get-History -count 10 | Format-Table -View time
Time custom view
figure 4

If I always want this view available, I can add the Update-FormatData command to my PowerShell profile script.

More Validation Error Messages

I've spent time recently in the newsletter demonstrating parameter validation techniques. One of the reasons I like [ValidateScript()] in PowerShell 7 is the ability to specify a custom error message. It turns out, this feature is available for a few other validation attributes as well.

It is supported in [ValidateSet()]:

Function Get-PSFileInfo {
    [CmdletBinding()]
    Param(
        [Parameter(Position = 0, Mandatory)]
        [string]$Path,
        [ValidateSet('Size', 'Age', 'Security', 'Owner',
        ErrorMessage = 'The value [{0}] is an invalid option. Valid values are Size, Age, Security, and Owner. Please try again.')]
        [string]$Option = 'Size'
    )

    Write-Host "Processing $Path using option $Option"
}

Here it is in action:

PS C:\> Get-PSFileInfo C:\scripts\Get-AccessMaskDecode.ps1 -Option Age
Processing C:\scripts\Get-AccessMaskDecode.ps1 using option Age
PS C:\> Get-PSFileInfo C:\scripts\Get-AccessMaskDecode.ps1 -Option foo
Get-PSFileInfo: Cannot validate argument on parameter 'Option'. The value [foo] is an invalid option. Valid values are Size, Age, Security, and Owner. Please try again.

You can also use it with [ValidatePattern()]:

Function Get-PSFileInfo {
    [CmdletBinding()]
    Param(
        [Parameter(Position = 0, Mandatory)]
        [ValidateScript({Test-Path $_},
        ErrorMessage = 'The specified path, {0} could not be found'
        )]
        [ValidatePattern('\.ps(m)?(1(xml)?)?(d1)?$', ErrorMessage = 'The path does not look like a PowerShell-related file.'
        )]
        [string]$Path,
        [ValidateSet('Size', 'Age', 'Security', 'Owner', ErrorMessage = 'The value [{0}] is an invalid option. Valid values are Size, Age, Security, and Owner. Please try again.')]
        [string]$Option = 'Size'
    )

    Write-Host "Processing $Path using option $Option"
}

Notice that I am combining validation tests for $Path.

PS C:\> Get-PSFileInfo c:\scripts\oops.ps1
Get-PSFileInfo: Cannot validate argument on parameter 'Path'. The specified path, c:\scripts\oops.ps1 could not be found
PS C:\> Get-PSFileInfo C:\work\0wq51y1v.dat
Get-PSFileInfo: Cannot validate argument on parameter 'Path'. The path does not look like a PowerShell-related file.
PS C:\> Get-PSFileInfo C:\work\a.ps1
Processing C:\work\a.ps1 using option Size

Using a custom error message lets your command easier to use and understand when something goes wrong.

A Scripting Challenge

Finally, I have a new scripting challenge for you. I hope you are taking advantage of these as I think they are great learning tools. For this month, write a PowerShell function to query the Win32_UserProfile class on a local or remote computer. The output should look like this:

Name           : systemprofile
LastUseTime    : 11/19/2024 1:55:44 PM
Path           : C:\WINDOWS\system32\config\systemprofile
PathCreated    : 4/1/2024 3:26:07 AM
PathModified   : 6/25/2024 3:23:58 AM
Size           : 165896534
Special        : True
Loaded         : True
PSComputerName : JEFFDESK

As usual, I have bonus elements if you find this too easy:

  • If the profile has no files show a size value of 0
  • Allow the user to select a profile by name
  • Use a type name for the output object so that you can write a custom format file
  • Optimize the function for performance

You will need to think about the code you want to run and where to run it. I'll share my solution later next month.

Summary

We're rapidly reaching the end of another year and I want to sincerely thank my premium subscribers show support makes this newsletter possible. Free subscribers can upgrade at any time, and likewise cancel at any time. But while a premium member, you will have access to all of the previous issues in the archive. See you next month.

(c) 2022-2025 JDH Information Technology Solutions, Inc. - all rights reserved
Don't miss what's next. Subscribe to Behind the PowerShell Pipeline:
Start the conversation:
GitHub Bluesky LinkedIn About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.