Category Archives: Quick Learn

Practical examples of PowerShell concepts gathered from specific projects, forum replies, or general PowerShell use.

Applying an Automation Mindset to Household Tasks II

Did you ever read this post before? It wasn’t about PowerShell so much, as it was about taking our efficiencies in automation and using them in regular life, too.

Do you know how every version 1.0.0, is nothing more than a place holder for the first version to come after it? You know… 1.0.1, or 1.1.0, or maybe even 2.0.0. If you read that post, then you may remember these photos.

Its design was far from perfect. The diameter of the hose (used to remove dirty water from the pool filter) is 2″. It’s the exact same diameter as the entire piece of white pipe I found at The Home Depot. I looked and looked everywhere and I finally found something better! This had a smaller diameter pipe where the hose would attach while still being 2″ and threaded at the other end. The problem was (as you can likely imagine), that the hose could barely handle being wrapped around that 2″ end. It was unbelievably difficult to attach and it never took long for the hose to begin splitting and eventually failing. Enter, version 2.0.0.

Like any automation before it (in the actual automation world), I haven’t looked back. Here’s the newest backwash hose for my pool. It’s made life much easier! New ideas, improvements, and proper implementations tend to do that. Don’t worry, we’ll get back to discussing PowerShell soon, but just in case someone read the previous piece and would appreciate the follow-up, then here you go.

A Book: PowerShell to C# and Back

A new Leanpub book project was announced recently. It was June 25, 2020. Perhaps, that day will go down in history for being more than just another day attached to this … year. It may for me, for reasons outside of the current pandemic.

I didn’t realize this was the exact date for which I was waiting, but now, this date has meaning. For several years, I’ve believed that the PowerShell community was missing a book. I’ve even mentioned it. For many of us, PowerShell has opened doors, and possibly interests that we may have not known we even had.

A part of me has always wanted to be a developer, and perhaps that’s why as a systems administrator, I gravitated toward PowerShell, and VBScript before that. This was after, and even in conjunction with, some work with web-based technologies. I know where my scripting skills are going to lead me; it’s just not been fully realized yet. Learning C# would give me an advantage in the process and progress that I feel is the direction of my career. I can’t wait to further help support this book. It’s only 5% complete and here I am already.

Even as I knew I’d read this book someday, I’ve decided to volunteer my time and energy in any way I can, to give this book an upper hand. I offered myself in any way in which I can help make this effort a positive one. I’m going in not knowing C# very well, but with every belief that by reading and helping with this project, my coding skills will absolutely improve. Maybe even my PowerShell too. I’m going to use this book and the opportunity it will provide me to push forward and learn a new set of skills. Perhaps you’ll do the same.

The name of the book is PowerShell to C# and Back. In my opinion, and as I’ve already stated, this book has been needed for many years. After spending greater than seven years with PowerShell now, I’ve often wanted to move toward C# development. I think this is going to be that turning point. I look forward to finding out, and I’m grateful that I’m going to be a part of ensuring that you may, as well.

Prestaging Modules for PowerShell, Windows PowerShell

I recently wrote this to a colleague: “Finally, if you’re going to peer into the module manifest file (.psd1), you might also check ‘CompatiblePSEditions.’ If it includes both the ‘Core’ and ‘Desktop’ values, then you might consider installing modules in both $env:USERPROFILE\Documents\WindowsPowerShell\Modules and $env:USERPROFILE\Documents\PowerShell\Modules.”

He’s using SCCM, Software Center, and a Windows PowerShell form to install modules. My thought was to install it for both PowerShell and Windows PowerShell, regardless of which they were using and in reference to the manifest file. This would ensure a module was already in place provided the user switched between PowerShell and Windows PowerShell or moved from one to another one day. Not a bad idea, I suppose. If the duplicated module is never used, it’s okay as the disk space used would likely never really be noticed. Maybe one day, PowerShell will include this option itself. You install a module in PowerShell and include some yet-to-be-determined switch parameter, and boom, it’s ready in both locations.

This got me thinking. How would one write the code to do the conditional logic here? While it wasn’t my project, I couldn’t help myself from determining how I might write it. And, when it was done — and this has happened before — I wasn’t sure what to do with it. Enter, this blog post; its new home, or final resting place.

I wrote this to check four different modules on my computer. They were: (1) AWS.Tools.Common, (2) AWSLambdaPSCore, (3) ISE, and (4) ExchangeOnlineManagement. Each of these four contained a different value in the psd1’s CompatiblePSEditions. In the same order as above, there were (1) Core and Desktop, (2) Only Core, (3) Only Desktop, and (4) Neither Core nor Desktop.

Here’s what I wrote; we’ll discuss it further below. It was saved into a file named ModuleDeploy.ps1.

# Both Core AND Desktop.
$Path01 = "$env:USERPROFILE\Documents\PowerShell\Modules\AWS.Tools.Common\4.0.5.0\AWS.Tools.Common.psd1"

# ONLY Core.
$Path02 = "$env:USERPROFILE\Documents\PowerShell\Modules\AWSLambdaPSCore\2.0.0.0\AWSLambdaPSCore.psd1"

# ONLY Desktop.
$Path03 = "$env:SystemRoot\system32\WindowsPowerShell\v1.0\Modules\ISE\ISE.psd1"

# Neither Core NOR Desktop.
$Path04 = "$env:USERPROFILE\Documents\PowerShell\Modules\ExchangeOnlineManagement\1.0.1\ExchangeOnlineManagement.psd1" 

$Paths = $Path01,$Path02,$Path03,$Path04
Foreach ($Path in $Paths) {
	"`r`n||| Module: $(Split-Path -Path $Path -Leaf) |||"
	$Psd1File = Import-PowerShellDataFile -Path $Path

	Switch ($Psd1File.CompatiblePSEditions) {
		{$_ -contains 'Desktop'} {
			Write-Output -InputObject 'Copy to WindowsPowerShell directory.'
		} # End Condition.

		{$_ -contains 'Core'} {
			Write-Output -InputObject 'Copy to PowerShell directory.'
		} # End Condition.

		default {
			If ($PSVersionTable.PSEdition -eq 'Desktop' -or $null -eq $PSVersionTable.PSEdition) {'Copy to WindowsPowerShell directory (default).'
			} ElseIf ($PSVersionTable.PSEdition -eq 'Core') {'Copy to PowerShell directory (default).'} # End If-ElseIf.
		} # End Condition.
	} # End Switch.
} # End Foreach.

Let’s quickly cover what’s happening in the above code. Then we’ll view the below results for the execution in both PowerShell and Windows PowerShell. Showing both versions may clarify the reason for the If statement inside the above final/default Switch condition. We’re already jumping ahead.

First, we created four different path variables for four specific module manifest files (.psd1 files). The first one is to a module that includes both “Core” and “Desktop” in CompatiblePSEdition. The second only has “Core,” the third only has “Desktop,” and the final one has neither/nothing. This was mentioned earlier. These four variables and their assigned values are combined into an array, which we then iterate over inside of a Foreach loop.

During each iteration, we run through a Switch statement that indicates where each module would be copied (if we were actually copying the modules). Based on the value(s) in CompatiblePSEdition for each of the files, the first module would be copied to both the PowerShell and WindowsPowerShell modules, the second, only to PowerShell modules, and the third, only to WindowsPowerShell modules. The final copy is dependent on whether or not we’re using PowerShell or Windows PowerShell. Take a look at the results.

PowerShell 7.0.1

[PS7.0.1] C:\> . "$env:TEMP\ModuleDeploy.ps1"

||| Module: AWS.Tools.Common.psd1 |||
Copy to PowerShell directory.
Copy to WindowsPowerShell directory.

||| Module: AWSLambdaPSCore.psd1 |||
Copy to PowerShell directory.

||| Module: ISE.psd1 |||
Copy to WindowsPowerShell directory.

||| Module: ExchangeOnlineManagement.psd1 |||
Copy to PowerShell directory (default).

WindowsPowerShell 5.1

[PS5.1.18362.752] C:\> . "$env:TEMP\ModuleDeploy.ps1"

||| Module: AWS.Tools.Common.psd1 |||
Copy to PowerShell directory.
Copy to WindowsPowerShell directory.

||| Module: AWSLambdaPSCore.psd1 |||
Copy to PowerShell directory.

||| Module: ISE.psd1 |||
Copy to WindowsPowerShell directory.

||| Module: ExchangeOnlineManagement.psd1 |||
Copy to WindowsPowerShell directory (default).

That’s it. Just some code that needed a home. I’m fully aware that there’s better ways this could be done, and that there would be some copy duplication if this were dropped into production the way it is. Additionally, if a directory didn’t yet exist, it might have to be created. Still, it needed a home (as is), and I didn’t think about much more than the conditional logic involved to determine where a module would need to be deployed.

Applying an Automation Mindset to Household Tasks I

I started this blog post prior to listening to (and viewing) the PSConfEU’s free virtual mini-conference on June 2nd. I didn’t see/view it all, but I did enjoy what I was able to consume. While there were multiple members of the PowerShell Team present, there was Jeffrey Snover, too. The below quote from Jeffrey lined up so well with the topic of this post, that it had to be included.

“What gets people excited is solving problems.”

– Jeffrey Snover

It’s true. Finding quicker and better ways to automate is why many of us keep coming back. There’s an addiction here, and while I’ve used that term, I think it’s okay. It’s a healthy addiction. Yeah, we’ll go with that. We want the simplest, yet fastest, and cleanest possible solutions to our problems. We want our automation to shave off seconds of time when it’s possible. We’re out to save the seconds; we all know they add up.

I’m not one of those people. You know the ones: Every weekend it’s another house project. Sure I can do some things, but I’ve never tiled, built a bookcase from scratch, or added an addition to my house. I’m only so helpful, and for that I’m sorry. My wife deserves that guy, but no, I’m just a computer guy without all of the handyman skills. All as in most.

But, a couple of weekends ago it was different. You know those times where you write a piece of automation so perfect and revered at the moment it’s written, that you take a picture of your screen? You do that too, right? You literally take your phone out of your pocket, take a photo of VS Code, or whatever, and put it back in your pocket. I’m not the only one, am I?

Occasionally, I’m so pleased with a solution or I’m right in the middle of creating one, and I can’t keep working on it at that moment. So, while I’m away from the computer, I may want to be able to reference what I’m working through if I can. If you’re like me, you already know you’re going to be thinking about it. I’m obsessed with automation. If you are too, then a picture might make sense for you, as well. That’s the excitement in all of this. “How do I make this better when it does that, so I don’t ever have to do that, this way again.”

Let’s take a look at this image.

One of the household tasks around my home is backwashing the pool. This cleans out our filter by pushing pool water through the sand that’s inside of it. The water, without the sand, then exits the filter from the above PVC pipe. In order to do this, you have to prepare for where the water is going to go on exit. Recently, I had left a pool discharge hose connected to the pipe. Over time, it did exactly what one might expect when left outside in the elements. A hole was born. I fired up the backwash and I was drenched, head to toe in seconds, as I struggled to stop the pump. It was early enough that no one saw me; however, there were questions from my wife as to why my sopping wet clothes littered the patio at 8 a.m. in the morning. That was fair.

I was able to cut off the failed portion of the discharge hose, but what had I just signed up for? It took me at least 10 minutes to reattach the hose back to the pipe and backwash the filter. The hose is the same size in diameter as the opening of the pipe, so it took some muscle and help from a flat head screwdriver to get the hose in place. The hose, by no means, wanted to go over the pipe easily. Was I really going to do this once every seven days to keep the hose out of the elements? I guess so. But, that’s at least 40 minutes per month, lost. That’s not how we automation experts do things. I had a better idea, and I believe that the credit for my idea belongs to my career and how I think about problems and their solutions.

My solution was to attach the still working part of my discharge hose to an adapter I picked up at my local Home Depot. It had a female threaded end on one side. On the other, I permanently attached the discharge hose with a hose clamp. Brilliant, am I right? Okay, maybe it’s not brilliant.

When I need to backwash, I screw on the female adapter and toss the hose over the wall and out into the desert, as I had done before. I used it for the first time a weekend after these first two pictures were taken. Have a look! Like automation often is, it wasn’t perfect the first time. The best part is that I have other ideas to make it better. I need an adapter that 2″ female threaded end that goes down to something smaller. I think either 1 3/4″ or 1 1/2″. This will allow me to get the hose on easier, and fold it over itself, so there are two layers of the hose under the hose clamp. There was some small leakage the first go around, and like automation, we’re not really satisfied until all the additional fixes are implemented.

This next one wasn’t as nearly as ingenious. I literally cleared a path between a gate in my back yard down to the wash. Occasionally, we’ll carefully head out into the desert to explore. Until now, this meant walking a ridiculous path that was much too close to dips, holes, dead cactus, small bushes, etc. This has all been cleared. Simple, sure, but again, every trip to the wash after this change, is safer, easier, and it takes less time. Additionally, the water that pools up by the gate no longer comes back into the yard. Instead, it also uses the path, heading it’s way down to the wash behind the house. I may have only cleared a path here, but it wasn’t a requirement. I did it to make every other trip down there, just a bit faster, and a lot more safer. Speaking of which, I nearly stepped on a rattlesnake in the desert recently. I’d like to skip that from happening again and I hope this path can help.

While I’m no home improvement guru, I can still get excited about doing non-typical types of automation for my home improvement tasks. There’s a certain way a scripter or programmer has to think. You can try and teach it, but I think, based on what I did at home, that a part of it comes naturally. We’re problem solvers; we’re determined to find the best possible way to get something done. It’s not true, but my wife just thinks I’m lazy. It’s not about cutting corners and being lazy in general; it’s about making the things we have to do, as efficient as possible over time.

Read part II here.

PowerShell (Tab) Titles

I’m at a newer stage of life, and no, this has nothing to do with the current, worldwide pandemic or staying at home on a daily basis now. It’s hardly that serious. It has everything to do with my computer and the plethora of PowerShell tabs I have open. More so these days, it’s actually the elevated number of Windows Terminal instances running on my single, physical laptop. Sure, I could keep everything in a single instance of Windows Terminal, but I’m a fan of using multiple desktops. For me for example, it’s “here’s my general desktop, here’s my Bitbucket repository desktop, here’s my VS Code and Git desktop, and here’s my Docker desktop.” That’s four instances of Windows Terminal, although my VS Code and Git desktop would only have a Git Bash tab in Windows Terminal. Anyway, I needed a quicker way to know which desktop I’m currently on, and since I’m often at a PowerShell prompt, I thought I should help myself out.

This took me back to my earlier days learning PowerShell. See, those things really matter. I took advantage of my knowledge of the host program’s title bar. I was already doing that, but I needed to add more to it. Anyway, here’s what you’d see by default after I opened Windows PowerShell (5.1) and PowerShell (7.0.0), and then switched back to PowerShell.

As you can see, I use the tabs to ensure I know which version of PowerShell I’m using, although it’s mostly clear anyway due to the icons, host background color, etc. So yes, I have my version as clear as can be, but that’s not really the point today. I want to know which desktop I’m on with some help from PowerShell.

In order to know which desktop I’m currently using, I need to alter the WindowTitle. To be exact, I need to alter the $Host.UI.RawUI.WindowTitle. The value assigned to this property of this variable is displayed in the console host’s title, or in Windows Terminal, right on the tab. In this image, the WindowTitle simply says “PS 7.” I’ve been good with that, but now I want more.

The below function is the one I quickly authored for this purpose. It will allow me to alter the title, so that what I want is added to the already existing value, such as “PS 7” and “WPS 5.1.” Additionally, I added parameter sets and a second parameter, in order that I can reset the title back to its original value, and not just add something new.

Function Update-WindowTitle {
	[CmdletBinding()]
	Param (
		[Parameter(Mandatory, ParameterSetName = 'Add')]
		[string]$AdditionalTitle,

		[Parameter(ParameterSetName = 'Reset')]
		[switch]$Reset
	) # End Param.

	If (-Not(Get-Variable -Name OriginalTitle -Scope Global -ErrorAction SilentlyContinue)) {
		New-Variable -Name OriginalTitle -Value $Host.UI.RawUI.WindowTitle -Option Constant -Scope Global
	} # End If.

	If ($AdditionalTitle) {
		$Host.UI.RawUI.WindowTitle = "$OriginalTitle | $AdditionalTitle"
	} ElseIf ($Reset) {
		$Host.UI.RawUI.WindowTitle = $OriginalTitle
	} # End If-Else.
} # End Function: Update-WindowTitle.

Here’s a series of images that walk through some edits of the WindowTitle. I updated the title to show Docker, then Pandoc, and finally, reset its value. With multiple instances of Windows Terminal, on multiple virtual desktops, I can now quickly remind myself of the desktop I’m currently using. Although the tab title values are slightly different, I’ve included a gif below as well, in order to show movement between the desktops.

There’s more I could add to this function to ensure it works under odd conditions, but for now and for me, it’ll work just fine. Now to add it to my $PROFILE, so it’s always available.

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.

It’s Nearly the PowerShell PSBlogWeek. 2020.

It feels like it’s been forever. In fact, I had all but forgotten about PSBlogWeek until just recently, when it was reintroduced. So it’s coming; maybe you’ve heard about it before — maybe you haven’t. Some of the best PowerShell minds and their attached authors, come together to do a series of related blog posts — one day after the other, in order to give light to something in our community. It’s a topic, as you, a community member, should know. This time, it’s PowerShell 7.

If you haven’t been paying attention to it, then now’s your opportunity. Get in front of the curve, and pave your mind with the things you’re going to want to know before you need to know them. Before you wish you had just buckled down and read this upcoming content. Don’t be the one that puts this effort off. The goal is to help you, so do help yourself to this upcoming reading. Make a commitment now, to read them all!

At a time like this, I can’t help but think back to 2006 and the introduction of PowerShell 1.0. Back then, I loved Visual Basic Script (VBS). It’s been less embarrassing to admit that over the years, as I’ve put it behind me, and positioned PowerShell in its place. It’s also helpful that scripting and automation is something everyone has (should have, I suppose) to do now. Back then, you could be a Windows Systems Administrator without ever using the command line, much less a scripting language. Luckily, for people like me, that’s just not the case anymore.

The thing is, my move to PowerShell wasn’t immediate. I mostly stayed away from PowerShell for a few years, as it was in its infancy. I missed out on being a part of this community sooner. I missed out on those additional years that I could’ve had under my belt right now. Don’t make the same mistake I did.

You and PowerShell 7 are going to need each other, so don’t, for one .more. minute. pretend as though you’ll have time for this series later. Read each blog post; bring yourself up to speed before it’s a requirement. These authors are some of the community’s finest right now. There isn’t a better time to know and learn this stuff if you haven’t already been paying attention.

PowerShell 7 has been in development for quite some time now. I can’t do much with them, but I proudly have a copy of every preview installer for my system, on my system. I have both release candidates of PowerShell 7, too. As it’s about to go live-live, it’s time for you to try it out, and learn as much about it as you can. Right now. Use it in place of 5.1 and tell someone what doesn’t actually work. Like for real, file an issue on GitHub if something you’re doing in PowerShell 7 isn’t working as you’d expect it would (rc.2).

Beginning right here, right now, follow along. Fill that brain of yours with the things you’re only going to wish you had sooner. The schedule and topics are in the below post. Thank you for reading this introduction, and do know that I’ll be reading right alongside you. I’ll be writing something, too.

A New PowerShell PSBlogWeek is Coming

Forum Problem to Posted Solution and Article Post

I was reading the Stack Overflow (PowerShell) forum when I happened on a question that I decided I would take on. It reminded me of an old post I had written. In that post, Countdown Options, I wrote a countdown that overwrote itself as it counted down. Here’s s GIF from that post.

In the question on Stack Overflow, someone wanted to get away from the way Do-Until works. Here’s their, modified-by-me question:

I have this simple bit of code in a PS script where I want to get a simple yes or no answer…The problem is that when user inputs something other than y or n it reprompts on new line…How can I avoid the new line and make the code as simple and easy to understand as possible?

I didn’t bother taking my time to explain that this is how it works and that they should expect more from their users. Instead, I set out to use what I learned from my countdown article. Before we get to that solution, let’s look at how this works before the changes I implemented.

Do {
    $Answer = Read-Host -Prompt 'Found missing roles. Install them now? (y/n)'
}
Until ($Answer -eq 'y' -or $Answer -eq 'n')

According to the OP, the problem with the above option and below results is that it’s re-prompting on the next line if the answer doesn’t match “y” or “n.” That needed to be avoided, if possible.

Found missing roles. Install them now? (y/n): 1
Found missing roles. Install them now? (y/n): 2
Found missing roles. Install them now? (y/n): 3
Found missing roles. Install them now? (y/n): 4
Found missing roles. Install them now? (y/n): 
Found missing roles. Install them now? (y/n): 
Found missing roles. Install them now? (y/n): a
Found missing roles. Install them now? (y/n): b
Found missing roles. Install them now? (y/n): c
Found missing roles. Install them now? (y/n): n

In my example, we overwrite the previously entered value. Take a look at the slight modifications I’ve made to the code. Then, take a look at the GIF made for this article.

$Cursor = [System.Console]::CursorTop
Do {
    [System.Console]::CursorTop = $Cursor
    Clear-Host
    $Answer = Read-Host -Prompt 'Found missing roles. Install them now? (y/n)'
}
Until ($Answer -eq 'y' -or $Answer -eq 'n')

And with that, my work is done here, and over there on Stack Overflow. Well, until the next question, where I can hopefully make a positive impact.

Fake the Data when Missing the CSV

Earlier today, I had an opportunity to help a coworker run ICMP requests against a series of IP addresses. They were being pulled in from a CSV file, which contained hostnames with matching IP addresses. If for some reason the import failed, such as the file wasn’t where it was supposed to be, he wanted to supply a single IP address he knew would fail in place of the successful CSV import. I suspect this was for testing purposes. Whatever the reason, I obliged him in his quest and request. I leaned heavily on a recently written and published article of mine: Hash Table to CSV, Revisited. It lined up perfectly with what I was working toward in this instance.

The goal here was to fake out an upcoming Foreach loop in such a way that no coding changes needed to be made, whether or not the CSV file existed, etc. That was the end goal for this work: leave the later code doing what it had been doing previously.

We’re, mostly, going to start with the code. In this first example, you’ll have to believe me that the file IPs.csv does in fact exist, and it contains two columns. One is the hostname, which we don’t actually even use in this example, and the other is the IP address. As expected, this column contains a properly formatted IP address. As everything is in place, the CSV file is imported and saved in $IPAddresses. Following that, the code loops through the contents of our new variable. For every entry, it returns True or False depending on whether it’s able to return a successful ping request or not, against that IP address.

try {
    $IPAddresses = Import-Csv -Path 'C:\IPs.csv'
} catch {
    $IPAddresses = [PSCustomObject]@{Host = 'Test';IP = '8.8.8.9'}
} # End try-catch

Foreach ($IP in $IPAddresses) {
    If (Test-Connection $IP.IP -Count 1 -Quiet) {
        $true
    } Else {
        $false
    } # End If-Else.
} # End Foreach.

Pinging 8.8.8.8 [8.8.8.8] with 32 bytes of data:
Reply from 8.8.8.8: bytes=32 time=13ms TTL=50
Ping complete.
True
Pinging 216.58.193.206 [216.58.193.206] with 32 bytes of data:
Reply from 216.58.193.206: bytes=32 time=12ms TTL=53
Ping complete.
True

In this next example, we’ve altered the CSV file extension. This attempted CSV import will fail, and will therefore execute our code in the catch block. This will make it appear as though we’ve imported the CSV; however, because we recognize this IP address and its guaranteed to fail, we’ll know there was a problem with the CSV import.

try {
    $IPAddresses = Import-Csv -Path 'C:\IPs.csvvvvvv'
} catch {
    $IPAddresses = [PSCustomObject]@{Host = 'Test';IP = '8.8.8.9'}
} # End try-catch

Foreach ($IP in $IPAddresses) {
    If (Test-Connection $IP.IP -Count 1 -Quiet) {
        $true
    } Else {
        $false
    } # End If-Else.
} # End Foreach.

Pinging 8.8.8.9 [8.8.8.9] with 32 bytes of data:
Request timed out.
Ping complete.
False

There are other ways to handle this, yes. But remember, the co-worker needed data to be produced just as it would be if it worked (the import part), even though it didn’t. It had something to do with JSON, schemas, and Azure Log Analytics. It was good enough for him, so it was good enough for me, too.

PowerShell Ternary Operator If Only

I’m on the cutting edge, but my writing isn’t always there with me. When a new PowerShell feature is released, it’s not often me that’s telling you about it. That said, if it’s about trying something, or sharing something “cutting edge” at work, then that’s a different story. Well, this is a bit of a different story, too.

While it’s already been introduced, the Ternary operator joined us in one of the preview releases of PowerShell 7. As of this writing, PowerShell 7 rc2 has recently been introduced and the Ternary operator is still an experimental feature. This means, don’t use it in production just yet, as it could change and cause breaking changes for you. Before I go on, and in case you need to educate yourself about the Ternary operator, then read through this link: https://toastit.dev/2019/09/25/ternary-operator-powershell-7. I put this on Twitter recently and picked up several Retweets and Likes! Perhaps I should write about cutting-edge PowerShell topics. One person replied as though I wrote that article; nope, that wasn’t mine. This one is, however.

The Ternary operator allows you to simplify an If-Else statement, such as <condition> ? <If true> : <If false>. I liked the new inclusion in PowerShell the first time I saw it, but today I wondered something. I eventually tested it, and as a result, I’ve authored a new article.

So to recap for a half-second, the newly introduced Ternary operator in PowerShell 7 is a condensed way to structure an If-Else statement. Notice, I didn’t say an If statement, and yes, there’s a difference between the two. But, what if we get to the point one day when the Ternary operator isn’t an experimental feature, but we want to employ this operator in place of an If statement. Not an If-Else statement.

It came to me quickly, and even as simple as it is, I thought I’d write about it, in order that the person it doesn’t come to quickly one day, can read this post right here. Let’s start with the Ternary operator’s intended use: If-Else. First, we’ll start with an If-Else, and then move to the Ternary option.

If (Get-Item -Path 'C:\file' -ErrorAction SilentlyContinue) {
    'Located file.'
} Else {
    'Unable to locate file.'
} # End If-Else.

(Get-Item -Path 'C:\file' -ErrorAction SilentlyContinue) ? 'Located file.' : 'Unable to locate file.'

I tend to try and keep my PowerShell line length on the lower end. Do note that the next three examples are parsed properly across multiple lines, as one would expect. I did at least.

(Get-Item -Path 'C:\file' -ErrorAction SilentlyContinue) ? 'Located file.' :
'Unable to locate file.'

(Get-Item -Path 'C:\file' -ErrorAction SilentlyContinue) ?
'Located file.' : 'Unable to locate file.'

(Get-Item -Path 'C:\file' -ErrorAction SilentlyContinue) ? 
'Located file.' :
'Unable to locate file.'

Let’s consider an example that lines up better with an If statement. Notice that I first clear the $Error variable. I don’t recommend this in most situations, but for this example (and, other real-life experiences), it has its place.

PS &gt; $Error.Clear()
PS &gt; 1/0
RuntimeException: Attempted to divide by zero.
PS &gt;
PS &gt; $Error.Count
1
PS &gt; $Error.Count -eq 1 ? $Error.Remove($Error[0]) : 'Nothing.'
PS &gt;
PS &gt; # ^ That removed the error, but let's run it again.
PS &gt;
PS &gt; $Error.Count -eq 1 ? $Error.Remove($Error[0]) : 'Nothing.'
Nothing.

Now that you’ve looked over the above code, you can see why we don’t need an Else portion. Better stated: As we’re working with the Ternary operator,  you can see why we don’t need an If false portion. If the error doesn’t exist it just doesn’t remove it; I don’t need a report that there’s nothing happening. Speaking of nothing, here’s what we’re really after.

PS &gt; $Error.Count -eq 1 ? $Error.Remove($Error[0]) : $null
PS &gt;

Perfect — silence is golden. Makes you wonder though, right? Can we use $null in the If true portion? Of course. We can likely use it in both, at the same time? Yes, but I’m not going to go there. I mean…, how could that be helpful? Let’s do see an example of using $null in the If true portion, however.

PS &gt; $Error.Clear()
PS &gt; 1/0
RuntimeException: Attempted to divide by zero.
PS &gt;
PS &gt; $Error.Count -eq 1 ? $null : '$Error.Count is less than/greater than 1.'
PS &gt; $Error.Count
1
PS &gt; $Error.Remove($Error[0])
PS &gt; $Error.Count
0
PS &gt; 
PS &gt; $Error.Count -eq 1 ? $null : '$Error.Count is less than/greater than 1.'
$Error.Count is less than/greater than 1.
PS &gt;
PS &gt; $Error.Count -eq 1 ? $null : '$Error.Count is less than/greater than 1.'
$Error.Count is less than/greater than 1.
PS &gt;

And that’s it! Me writing about some newer additions, and you do not have to wonder, or test, if the Ternary operator handles things as you’d expect it might.