Tag Archives: PowerShell

Coding Novice, APIs, and PowerShell

I read a recent post on the technical writing subreddit, “How proficient in coding do you have to be to write API Documentation?” I jumped in and posted, as technology is my jam, and writing is my passion.

The author wanted to know if they need to know how to program to make use of an API—an Application Programming Interface. I don’t think so. I have well over 10 years of learning and working with PowerShell, and I don’t think anyone needs that to use an API. Maybe there will be a few things to learn, but not all of it. Why would you even want to focus on one language, as so many can make API calls? I think time would be better spent learning and understanding APIs before learning any one language. The languages the thread’s author mentioned were “HTML, CSS, JavaScript, and/or Python.” I could get some hate for this, but when I learned HTML and CSS I never once thought it was a programming language. I find it strange when people think it is. It’s a formatting language, like markdown. Do this to that, place that there, make this bold, etc. There’s no looping or conditional logic—I just never thought of either of those two that way.

My thought was to teach a bit about APIs with some help—the video linked at the end of this paragraph is great—and then, assume the reader has absolutely no experience with PowerShell. It can easily be installed on any operating system: Windows, macOS, or Linux. Once you have it installed, if you plan to [1], then let’s watch the below video. It is quick and painless and uses an easily understood analogy for APIs, and requests and responses. I’m going to assume that you, as the reader, have watched the video when I begin the next paragraph.

[1] If you’re on Windows, you can use the built-in Windows PowerShell if you’d rather, and not download and install PowerShell (yes, they’re different). If you’re on a Mac you can use the Terminal. If you’re on Linux you can use its built-in terminal, as well. Again, this is if you don’t want to use PowerShell for some strange reason.

Regardless of how you move forward, you now know enough about APIs for now. It’s a way in which you can send a request to, and get a response from, a web service. I absolutely love when I hear that a company is API first. For instance, my understanding is that AWS, or Amazon Web Services (Amazon’s Cloud computing platform), is API first. They build the API, then they build a UI or a website that calls the APIs. Then, they can write PowerShell and other CLI (command-line interfaces) that interact with the exact same API. Everything uses the same backend to get its results. If everything is API-driven, then there are numerous ways to do the same thing, to gather the same information. If there’s an API to start a virtual computer in their cloud, then you could do it from Python, PowerShell, Bash, and from the AWS Management Console—the AWS website. There are probably plenty of other languages that can interface with the exact same API, so why focus on any one single language? This is the point. Expose something in such a way that the language or shell program making the request doesn’t matter.

Before we use PowerShell and the Invoke-RestMethod command, or Bash and the cURL command, or whatever else you’ve chosen, let’s install Postman [2]. It can feel a little overwhelming, even for me, but we’re only going to use a small portion of it. You may have to create an account these days, and that’s perfectly fine to do. Once it’s installed, open it, sign in, etc. and let’s get to the main screen. You’ll probably begin in some default workspace. In my installation of Postman, I created a new workspace just for things related to my site and named it tommymaynard.com.

[2] While I’ve never used it, it appears that there’s a web-based version of Postman. I’m sure it’ll require registration, but it won’t require download and installation. My instructions will be for the installed version, however, I do suspect they’ll work similarly. Your choice.

Toward the top-left should be a plus (+) that allows you to create a collection. Think of a collection as a folder where we can separate like requests. It’s much like Workspaces, as, at minimum, it’s a means of separation, as well.

When given an option to name it, call it “ipify.org.” Once the collection is created, click the greater-than sign (>) to open the collection. There ought to be a message that says, “The collection is empty. Add a request to start working.” Click “Add a request” and rename the request to “IP address.” Now, take the below URL and copy and paste it where it says, “Enter request URL.”

https://api.ipify.org

When that’s in place, click Send on the far right. This will place your public-facing IP address in the lower, response pane. Again, think request (top-pane) and response (lower-pane). We send the waiter in, the waiter works with whatever is back in the kitchen—we don’t care—and then something comes back to us, at the table.

How did I know about this API, right? There are so many of them. There’s probably at least one for everything. At some point, you, like me, might be irritated you actually have to go to some website to find something out. If I could use Postman, or PowerShell, to securely check my bank balance, I would. Same interface for everything, please. I hate navigating 15 totally different websites; everything’s in a different place. It’s so annoying. APIs mean we don’t have to care where someone put something on their website.

I went off on a small tangent there. Anyway, APIs are most often documented, and the one we just used, is no exception. Take a look at the ipfiy.com website. It’s nothing but a shrine to how this API works. When there, scroll down to the “Code samples” section. There are 29 —yes, twenty-nine—different code examples: PowerShell, Python, PHP, Bash, Ruby, NodeJS, Go, C#, and more. Why would anyone learn one language? Postman isn’t even a language; do you even need a language? Postman may be all you need to write API documentation. You don’t have to be a coder to do this.

Let’s move back up on the ipify.org web page and take a look at the API Usage section for IPv4. There’s an option to include a query parameter. See if you can spot what’s different in the below image from what we saw previously. Right-click on our ipify.org collection and choose “Add Request” and see if you can’t rename it to “IP address (JSON).” Once your Postman looks like the below image, hit Send. Your public-facing IP address should appear in the response pane, too.

This request will include a query parameter inside the address. This is notated with the question mark (?), followed by a key-value pair. In our instance, the key-value pair consists of format (the key) and json (the value). When this is run, we return more than just text, but instead, a JSON object. Some systems that might make this request, might prefer it’s already formatted in JSON. It’s a data structure; it’s how—and this is going to sound crazy—we want the data to be structured. Different systems want certain things a certain way and so, if the API allows for it, we can get it back (in our response), the way we want it, without any manipulation on our part. Think about your food in that restaurant. Wouldn’t you prefer the spices are added to the entree while it’s cooking versus you, doing it at the table? It’s more work for you, and it may not turn out as well.

My computer has Windows PowerShell, PowerShell, Git Bash, and using Windows Subsystem for Linux (WSL), Ubuntu as well. In the below images, we’ll run through making use of this API in nearly all of them—we’ll skip Windows PowerShell. Before we take a look at that, however, take a look at this.

It’s over the far-right of Postman. This amazing piece of software just gave you all you need to invoke this API in multiple languages, shells, etc. The cURL command is what can be used in the Mac Terminal or in a Linux Bash shell.

So, let’s try it. Here are a few API requests and responses from my machine. WSL is first, followed by Git Bash, followed by my favorite, PowerShell. The first two don’t use the format query parameter, while the last one does. A note about that after the images, however.

The PowerShell Invoke-RestMethod command was written to take returned JSON objects and automatically convert them into PowerShell objects. Therefore, the second command, ConvertTo-Json, is included to convert it back into JSON. So, that’s what’s happening there.

Why you’d learn an entire language to do this, I have no idea. Learn as much as you need now, and then go back and fill in what you want, and what becomes necessary to know at a later date. And, this is from someone that hates only knowing parts of things. I also understand time constraints and that there’s a better way to get up to speed with APIs than to learn an entire programming language, or two, or a couple of formatting languages. Focus on learning more about APIs for now; there’s plenty more to know than what we covered here.

Accounts Continued – Azure with PowerShell III

Posts in this Series

In the last post, we took a look at the Connect-AzAccount command, stored its output in a variable using the Outvariable parameter and explored it. I think we ought to go through a few of the other commands in the Az.Accounts module and see why we didn’t need to dig in such as we did. As I said previously, it was a good exercise, but it’s almost like the cmdlet developers wanted to provide us with an easier way to gather Azure account-related information. We’ll know more soon enough. First things first, let’s pull back only the Get commands from the Az.Accounts PowerShell module. Again, we’re going to use some formatting commands, but because we only want to view our results on the screen.

Get-Command -Module Az.Accounts -Verb Get | Format-Table -AutoSize
CommandType Name                         Version Source
----------- ----                         ------- ------
Alias       Get-AzDomain                 2.8.0   Az.Accounts
Cmdlet      Get-AzAccessToken            2.8.0   Az.Accounts
Cmdlet      Get-AzConfig                 2.8.0   Az.Accounts
Cmdlet      Get-AzContext                2.8.0   Az.Accounts
Cmdlet      Get-AzContextAutosaveSetting 2.8.0   Az.Accounts
Cmdlet      Get-AzDefault                2.8.0   Az.Accounts
Cmdlet      Get-AzEnvironment            2.8.0   Az.Accounts
Cmdlet      Get-AzSubscription           2.8.0   Az.Accounts
Cmdlet      Get-AzTenant                 2.8.0   Az.Accounts

If you read the last post, then commands such as Get-AzEnvironment and Get-AzContext might seem to make sense here. The nouns in those commands were the two, base properties in our $AzOutput variable. Let’s see what they return and compare it to what we saw in the output variable in the Accounts – Azure with PowerShell II post.

Get-Environment
Name              Resource Manager Url                  ActiveDirectory Authority          Type
----              --------------------                  -------------------------          ----
AzureGermanCloud  https://management.microsoftazure.de/ https://login.microsoftonline.de/  Built-in
AzureCloud        https://management.azure.com/         https://login.microsoftonline.com/ Built-in
AzureUSGovernment https://management.usgovcloudapi.net/ https://login.microsoftonline.us/  Built-in
AzureChinaCloud   https://management.chinacloudapi.cn/  https://login.chinacloudapi.cn/    Built-in

versus

$AzOutput.Environments
Key               Value
---               -----
AzureGermanCloud  AzureGermanCloud
AzureCloud        AzureCloud
AzureUSGovernment AzureUSGovernment
AzureChinaCloud   AzureChinaCloud

It’s clear, that by default the Get-Environment cmdlet returns the information we saw previously. Still, by default, it includes some additional information. While I didn’t show the output in the previous post, I did include $AzOutput.Environments.Values. This will give the same results as above, but let’s use what they’ve provided us. The same goes for using Get-AzContext even though it produces the same exact information as $AzOutput.Context. Whoa, whoa. It doesn’t, we lost the Name property using our output variable. The cmdlets, that we should use, create complete objects; again, let’s stick with these.

Get-AzContext | Format-List
Name               : Free Trial (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) - xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - tommymaynard@xxxxxx
Account            : tommymaynard@xx.xxx
Environment        : AzureCloud
Subscription       : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Tenant             : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
TokenCache         :
VersionProfile     :
ExtendedProperties : {}
$AzOutput.Context | Format-List
Name               :
Account            : tommymaynardxxxxxx
Environment        : AzureCloud
Subscription       : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Tenant             : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
TokenCache         :
VersionProfile     :
ExtendedProperties : {}

There was more to the Get started with Azure PowerShell document that I overlooked. Let’s head back there. If you’re new to PowerShell then it’s a must that you learn how to find commands using Get-Command and even Get-Module. Also, use Get-Help to explore information about a specific command. These are some of the early-on basics to learn when you’re new to PowerShell. No one knows what every command does; they just know how to find out. A portion of this page covered this topic, too.

One final thing I found on the sign into Azure section of the page, was the UseDeviceAuthentication parameter. Using this, we don’t have to supply a username and corresponding password, but instead, using this parameter generated a device code in PowerShell to use at a specific URL, which is all included in the image below.

Reading that, lead me to the Sign in with Azure PowerShell page. I should’ve known, there are plenty of other ways to authenticate to Azure, besides using the two interactive methods we’ve discussed so far. One is signing in with a service principal using password-based authentication and the other is certificate-based authentication. There’s also signing in using a managed identity. The rest you can explore at your leisure, but we should work through using a service principal using password-based authentication—why not!?

In the password-based authentication link above, the first thing to notice is the need to invoke the New-AzADServicePrincipal command. Before we invoke a command that will most likely make changes, such as a New- command likely would, we want, no we need, to learn more. First, in what module is the command included?

Get-Command -Name New-AzADServicePrincipal | Format-Table -AutoSize
CommandType Name                     Version Source
----------- ----                     ------- ------
Function    New-AzADServicePrincipal 6.0.0   Az.Resources

What’s the purpose of the Azure PowerShell Az.Resources module?

Get-Module -Name Az.Resources | Select-Object -Property Name,Description | Format-List
Name        : Az.Resources
Description : Microsoft Azure PowerShell - Azure Resource Manager and Active Directory cmdlets in Windows PowerShell and PowerShell Core.  Manages subscriptions, tenants, resource groups, deployment templates, providers, and resource permissions in Azure Resource
              Manager.  Provides cmdlets for managing resources generically across resource providers.
              For more information on Resource Manager, please visit the following: https://docs.microsoft.com/azure/azure-resource-manager/
              For more information on Active Directory, please visit the following: https://docs.microsoft.com/azure/active-directory/fundamentals/active-directory-whatis

What does the New-AzADServicePrincipal command do?

Get-Help -Name New-AzADServicePrincipal | Select-Object -Property Name,Synopsis
Name                     Synopsis
----                     --------
New-AzADServicePrincipal Adds new entity to servicePrincipals

Is there a Get- Version of the command (Get-AzADServicePrincipal)?

Get-Command -Name Get-AzADServicePrincipal | Format-Table -AutoSize
CommandType Name                     Version Source
----------- ----                     ------- ------
Function    Get-AzADServicePrincipal 6.0.0   Az.Resources

And if so, what output does it produce?

Get-AzADServicePrincipal | Select-Object -Property AppDisplayName
AppDisplayName
--------------
Office 365 Configure
Azure SQL Managed Instance to Microsoft.Network
Microsoft Graph
Microsoft Modern Contact Master
Azure Resource Graph
Billing RP
Jarvis Transaction Service
AIGraphClient
Microsoft_Azure_Support
Windows Azure Security Resource Provider
Azure SQL Database Backup To Azure Backup Vault
Azure Data Warehouse Polybase
Microsoft.Azure.ActiveDirectoryIUX
Microsoft Azure App Service
Policy Administration Service
Azure Portal
Azure SQL Virtual Network to Network Resource Provider
Azure Classic Portal
Azure Monitor System
Microsoft.SupportTicketSubmission
Azure ESTS Service
Windows Azure Active Directory
Azure Traffic Manager and DNS
Microsoft.Azure.GraphExplorer
Microsoft App Access Panel
Microsoft Azure Signup Portal
Signup
Microsoft.SMIT
Windows Azure Service Management API

Okay, we should feel as though we know a little bit more about our next move. First, however, check out the Azure Portal view of this output. The instructions can be found in the View the service principal subtopic. Sure enough, it looks almost exactly like the above output when we don’t use | Select-Object -Property AppDisplayName, and display the GUIDs.

First, we need to obtain and store our Tenant ID.

$TenantId = (Get-AzContext).Tenant.Id
$TenantId
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Then we’ll use the New-AzADServicePrincipal command, among a few other commands to create a new Service Principal and get connected to Azure using it.

$ServicePrincipal = New-AzADServicePrincipal -DisplayName SPName
$ServicePrincipal
DisplayName Id                                   AppId
----------- --                                   -----
SPName      xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

After this step, we can go back into the Azure Portal and find a new entry. The command worked!

The $ServicePrincipal variable includes more information than its output displays by default. We should be getting used to that by now. It includes a PasswordCredentials property that includes several other nested properties.

$ServicePrincipal.PasswordCredentials
CustomKeyIdentifier DisplayName EndDateTime          Hint KeyId                                SecretText                               StartDateTime
------------------- ----------- -----------          ---- -----                                ----------                               -------------
                                5/30/2023 1:08:45 AM Qcw  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Qcw8Q......oRLla5 5/30/2022 1:08:45 AM

One of those nested properties is SecretText. That is our password and we’re going to use it to create a credential object—the combination of a user and its password.

$ServicePrincipal.PasswordCredentials.SecretText
Qcw8Q......oRLla5

As we’ll see, the username is the AppId’s GUID. When we supply both it and the password, as is done in the below PowerShell, we’ll have our complete PSCredential object.

$PSCredential = Get-Credential -UserName $ServicePrincipal.AppId
PowerShell credential request
Enter your credentials.
Password for user xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx: ****************************************

Using the PSCredential object, we can 100% authenticate to Azure using PowerShell in another manner. We haven’t researched it or discussed it yet—although we should have—but the purpose for why we’d use a service principal should be included. The first paragraph on the Create an Azure service principal with Azure PowerShell page explains it well. Read that. Back with more Azure PowerShell in time!

Connect-AzAccount -ServicePrincipal -Credential $PSCredential -Tenant $TenantId
WARNING: The provided service principal secret will be included in the 'AzureRmContext.json' file found in the user profile ( C:\Users\tommymaynard\.Azure ). Please ensure that this directory has appropriate protections.

Account                              SubscriptionName TenantId                             Environment
-------                              ---------------- --------                             -----------
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx                  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx AzureCloud

It Begins – Azure with PowerShell I

Posts in this Series

I had a conversation with someone recently, which reminded me of an event in my life. I was sitting in the yard, maybe up to 15 years ago, pulling weeds. In southern Arizona, getting rid of the weeds is what you do, one way or another. Either you do it, or you pay someone else to do it. While I’ve yet to pay for such a service, I remember thinking, why am I doing this? Why am I sitting in the yard during the weekend when I could be sitting in the house and learning more about this IT career of mine? My competition is in there … figuratively. They’re learning more than me now, sitting here, learning nothing, except which weeds are going to leave a sticky residue on my fingertips, or poke me, or never come out of the earth with the roots intact, ever. Every minute I’m not learning more about what I do, someone else is gaining an advantage over me. More dollars, more vacations, more iRobot vacuums, which I hear are great–that’s what this person was telling me–but how would I know?

Don’t get me wrong, everyone has to have non-work-related hobbies and things to do, but pulling weeds probably isn’t it.

So, I am beginning my journey into Azure, alongside PowerShell. Or just maybe, I have that backward: My journey into PowerShell, alongside Azure. No. I have over 10 years’ worth of PowerShell experience and we’ll say a couple of years of Azure? I did receive an Azure (and M365) Fundamentals certificate, so maybe I know something. It’s been a while though, as I work closely with AWS five days a week. Regardless of how I think about this though, I’m going to learn one with the help of the other. I’ve done this before; I started with the cmdlets first: “I’m just starting to get my hands wet with Microsoft Lync. As I often do, I use the Windows PowerShell cmdlets to help learn more about a product; I did this same thing with Hyper-V.” Then, once I’m comfortable with those, maybe I head into the UI and see if I can duplicate what the commands do.

I recommend you do as I did, and begin with the two, below posts to get started with Azure PowerShell. These are quick, easy reads, one of which will assist with ensuring you have the Azure PowerShell module installed.

While I’ve been writing about PowerShell for a while, I think with this series, I’m going to approach things as though my visitors aren’t as experienced with PowerShell as my normal audience. So, if you’re my normal audience, some of the PowerShell concepts I mention may seem a touch basic, but with good reason. Knowing PowerShell and learning how to make things happen, is much different than knowing a product or service, and then learning PowerShell.

And bonus, the 8.0.0 version of the Az PowerShell module was released, today. That’s right, Tuesday, May 24, 2022–the first day of this series. The below, Find-Modulecommand, searches the PowerShell Gallery for the module and populates the $AzModuleInfo variable full of information–not just the Name, Version, and PublishedDate, although Select-Object does filter the output after the variable collects it all. Be sure to inspect the variable closely to view all the things that it contains. While Find-Module won’t install the module, Install-Module will. Before you run away with that command, though, read the two above links. More soon!

Find-Module -Name Az -OutVariable AzModuleInfo | Select-Object -Property Name,Version,PublishedDate

Name Version PublishedDate
---- ------- -------------
Az   8.0.0   5/24/2022 1:05:02 AM
$AzModuleInfo | Select-Object -Property *
Name                       : Az
Version                    : 8.0.0
Type                       : Module
Description                : Microsoft Azure PowerShell - Cmdlets to manage resources in Azure. This module is compatible with PowerShell and Windows PowerShell.
                             For more information about the Az module, please visit the following: https://docs.microsoft.com/powershell/azure/
Author                     : Microsoft Corporation
CompanyName                : azure-sdk...

It is All About the PowerShell Users

There was a recent series of Twitter posts from Jeffery Snover, the inventor of PowerShell himself. It started with the 15th birthday of PowerShell, which happened recently. I think I have captured it all here; have a read. You may have missed it and … I do not think that anyone should.

Jeffery said, “The thing I am most proud of is our unwavering and complete focus on our users – it is about them not us.” It goes on, “Notice that I used the word “user” and not “customer”. Why? Because PowerShell is free. It is a benefit of being a Microsoft customer. That distinction is critical.”

That was what allowed us to backport to previous OSes instead of using it to force people to the latest version of Windows. That was what allowed us to ship V2, V3…V7 when some thought we should stop after V1. That was what allowed us to open source our code.

There was more, That was what allowed us to partner with VMWare, Amazon, Google and many other customers that our users also used. That was what allowed us to port to Linux. And so many other things. I honestly cannot think of a more USER-centered project in the entirety of MSFT history. PowerShell is about our USERS not about us THAT is what I am most proud of.

I get that PowerShell has long been an investment in users. Back in 2013, Jeffery wrote this Tweet: “The team made a promise so important, we called it our sacred vow: Learn PowerShell and we’ll do everything we can to make it the best investment you’ve ever made. It’s 13 years later and I can confidently say that we have kept faith with that vow.

This was obviously a couple of years ago.

I bring all this up because, for me, it has been about the users too. I am into PowerShell so much, that I do what I can to bring others along with me. Microsoft has given us a tool to automate, and in doing so, given us a way to become highly efficient, and to ensure accuracy. Those are the words to live by when it comes to PowerShell: efficiency and accuracy.

So, if you didn’t get to read these comments from Jeffery Snover before, I am glad you have now! Hopefully, they will stick in your brain, as they do mine.

PowerShell and the LAPS Windows PowerShell Module

There’s a difference between Windows PowerShell and just PowerShell. You know that, right? You know what the difference is, yeah? If not, then do know that Jeff Hicks wrote a great blog post about it recently titled “What the Shell is Happening?” I’m going to assume you’ve read this as I go along, so you might just do that quickly if you haven’t already.

Windows PowerShell and PowerShell really are two different things. We’re moving into a territory where you’ll be able to know how much someone knows about Windows PowerShell/PowerShell based on how they are discussed. It’s going to be like PowerShell quoting rules: You can tell a good deal about someone’s Windows PowerShell/PowerShell knowledge based on when they do and don’t use single and double-quotes.

I was recently looking at some documentation when I happened upon the LAPS Windows PowerShell module. If you’re looking for a strangely named module then look no further; it’s AdmPwd.PS. Seriously. How about just calling it LAPS!? Up until that point in the documentation review I was doing, everything I tried was working in PowerShell — commands from the Hyper-V module, commands from the ActiveDirectory module (read the Edit section at the bottom of this post when done reading this post), and all of the we-ship-these-with-the-product Microsoft modules and commands.

The LAPS module — we’ll just call it that — is stored not only as a Windows PowerShell module, but it’s stored in the C:\Windows folder. That’s C:\Windows\System32\WindowsPowerShell\v1.0\Modules to be exact. While tab-completion didn’t work on the module’s name, the Import-Module cmdlet did work, but not without a warning.

The warning message above has to do with how PSRemoting sessions work. First off, a PSRemoting session is usually done from one computer to another. In this instance, however, it’s doing a PSRemoting session from and to the same computer: mine. In that remoting session, it’s loading up Windows PowerShell (powershell.exe [not pwsh.exe]). As can be seen below, running Get-PSSession will provide information about the Windows PowerShell (or WinPSCompatSession) PSRemoting session. Notice the -1 value for the IdleTimeout. Without any reading and research, I’m going to say that it means the session doesn’t timeout. I left mine up for an hour or so, and sure enough, the LAPS commands continued to work without having to recreate a PSRemoting session.

In any PSRemoting session, whether to the same computer or another, we get deserialized objects as the results. That means that the final results, that end up on the source computer from the destination computer, do not consist of live PowerShell objects. I’m sure you can read more about it, but the result/output is serialized into XML, sent across the wire from one computer to the other, and then deserialized. This has to do with how to quickly move data from one machine to another. In our case, even though it’s the same computer, it’s still making use of the network card and serialization/deserialization. It’s still doing this even if your PSRemoting session is to and from the same computer.

But there’s another way we can use this module. As mentioned in our warning message, Import-Module includes a SkipEditionCheck switch parameter. According to the documentation, this forces the command to skip evaluating CompatiblePSEditions in a module’s manifest file. This is a key within the file that indicates whether or not a module can be used for Windows PowerShell, which uses the term “Desktop” if it can, or PowerShell, which uses the term “Core” if it can. If a module was designed to work with both Windows PowerShell and PowerShell, it would include both terms. The LAPS module was written before CompatiblePSEditions was added to the module manifest files (.psd1 files).

When this parameter is used, it doesn’t look for CompatiblePSEditions, as stated, and appears to load the LAPS module properly. Well, properly enough that I was able to test the Get-AdmPwdPassword command. Before we see some proof supporting that claim, let’s take a look at some very important information from the Import-Module documentation.

It turns out it’s very likely this module and its related commands wouldn’t have worked using the SkipEditionCheck parameter, but so far they do. Oh, I didn’t mention this, but in the documentation for Import-Module and the SkipEditionCheck parameter, it does mention the path where the LAPS module is located, “Allows loading a module from the “$($env:windir)\System32\WindowsPowerShell\v1.0\Modules” module directory into PowerShell Core when that module does not specify Core in the CompatiblePSEditions manifest field.” So, a part of how this works is also due to the module’s location in the file system. Now, here’s our successful invocation of the Get-AdmPwdPassword command.

By using this method — and we’re lucky we can it seems — we skip creating a PSRemoting session from/to the same computer. Therefore, we aren’t forced to work with deserialized objects. Typically, it’s fine if you do, but with the LAPS module and PowerShell, it’s not a requirement. Take a look at the TypeName value above. That’s a clear indication that we are working with live PowerShell objects. We didn’t see this earlier in our PSRemoting session, but if we had, it would’ve said, “Deserialized.AdmPwd.PSTypes.PasswordInfo.”

Edit: A few days have passed since I first published this post. I didn’t know it then, but I do now! The ActiveDirectory module in PowerShell also creates a PSRemoting session! It looks like it’s running in PowerShell, but it’s really running in Windows PowerShell “behind the scenes.” I’ve had no problems dealing with a deserialized objects, by the way. This is because any piping I might do happens on the remote computer (my computer, but yeah), before the serialization/deserialization process.

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.

PowerShell Function with a Function Full of Functions

Yeah, you read that title correctly. Or did you? You might want to double-check; I had to write it a few times.

I did that thing where I authored some PowerShell code and I don’t know what to do with it. While it was written during some updates to a common function template, I’m not yet sure it’ll be implemented there. Therefore, I figured I’d share it. Additionally, this will give me a place to store it, in case I do use it. Some day you may ask yourself, can I create a function that creates a global function, that contains other functions? Yes. Today, I intend better help answer that question and include an example.

All this said I’m not here to explain why you might want to do this. Again, I’m not even sure if I’ll need this. So Internet and you, it’s yours. Edit: Some time has passed since I wrote this opening, so yes, I’ll be using this in production.

I have a function template that I use when writing any new function(s). Its number one goal is to ensure that all my functions, or tools, include identical logging. It’s identical, whether it’s written to the screen, to a file, or to both. I feel like I’ve written that before.

When the function is complete, and it’s written a log file, I have a global function that is created, too. This allows you to quickly open the log file, open just the location of the log file, or read in the contents of the most recently created log file and create an object from each line. If a single function is created, then the user isn’t required to remember more than a single function name. Sure, they might have to remember the parameter values for the single functi– okay, no they wouldn’t. They can tab through those.

Code Example

Function Show-FunctionTemplate {
    'You''re inside the Function Template (1st).'

    Function Global:New-FtFunction {
        Param (
            [Parameter()]
            [ValidateSet('1','2','6')]
            [array]$Function
        ) # End Param.

        Switch ($Function) {
            '1' {Function Test-One {'Test-One'}; Test-One}
            '2' {Function Test-Two {'Test-Two'}; Test-Two}
            '6' {Function Test-Six {'Test-Six'}; Test-Six}
        } # End Switch.
    } # End Function: New-FtFunction.

    'You''re inside the Function Template (2nd).'
} # End Function: Show-FunctionTemplate

Show-FunctionTemplate

New-FtFunction
New-FtFunction -Function 1
New-FtFunction -Function 1,2
New-FtFunction -Function 1,2,6

Discussion

The overall goal in this code is that we’ve created a function named Show-FunctionTemplate, in our local, and in this case, global scope. Done.

Inside this function (when it’s invoked), we echo a couple of statements about being inside the function. Additionally, and in between those two statements, we create a second global function named, New-FtFunction. When Show-FunctionTemplate is done executing, New-FtFunction can then be invoked.

When the New-FtFunction is invoked it won’t do anything unless one of the parameter values that it’s expecting is included. In the above code sample, I’ve included some examples. The below image shows the output returned from executing these.

Output of the New-FtFunction function.

Perhaps it should be noted, but the most nested of functions, both define the function and invoke it — one right after the other. But in obvious news now, I can do it. I can make a (global) function, make a global function responsible for making and invoking other functions. I think this is going into my function template. I really do. Edit: It did.

Run One from the Other

It wasn’t but a few days ago that I saw something that piqued my interest. It was an example of running a command against PowerShell Core (pwsh.exe) from Windows PowerShell (powershell.exe), or maybe it was the other way around? Whichever, I’ve included an image that shows this working in both directions. That’s to say it’s an example of running the same command in both versions of PowerShell, from each version of PowerShell.

I do suspect the picture will help explain things, if that last paragraph didn’t.

In the above image, we can see that we’ve run a command against both Windows PowerShell (5.1) and PowerShell Core (6.1). In both consoles, we’ve run the same command; it’s included below. Its goal was to return the version of PowerShell, for both versions of PowerShell.

'powershell.exe','pwsh.exe' | ForEach-Object {
    & $_ -Command {$PSVersionTable.PSVersion}
}

Our results are identical; we knew, we were running 5.1 and 6.1. Neat, right!? Keep this trick in your back pocket, as I do suspect it may be helpful for one of us, one day. Maybe it won’t have anything to do with obtaining the version, of a version of PowerShell, and instead someone will find another use.

Here’s a start. The thought I had was, can I run an Active Directory command in Windows PowerShell (powershell.exe) from PowerShell Core (pwsh.exe)? You bet I can.

In the below example, I’ve returned my GivenName from Active Directory using Windows PowerShell, from PowerShell Core. That could be potentially helpful for someone in some yet-to-be-thought-of project.

Especially in the case of Active Directory, it’s important to remember that each time a command is run, it must import the Active Directory module. Consider that each command is spinning up a new powershell.exe process. For all we know, this may be the reason why the WindowsCompatibility module uses PowerShell sessions, and not PowerShell processes.

In this next example, we issue a Windows PowerShell only command that’s tied to a builtin Microsoft Windows PowerShell model. We issue the Get-LocalUser cmdlet out of the Microsoft.PowerShell.LocalAccounts module. While it’s still importing a module into a powershell.exe process, it loads quicker than the ActiveDirectory PowerShell module did.

I’m going to need to call this a night here soon, but I keep finding more things to try. Like this example. There’s two commands. The first one runs Windows PowerShell, which runs PowerShell Core to determine the version of PowerShell Core.

The second one runs Windows PowerShell, which runs PowerShell Core, which runs Windows PowerShell to determine the version of Windows PowerShell. The image for these examples might be easier to understand, too.

Alright, that’s it for now. Perhaps I’ll come up with some other ideas to try another day. I need to put this post, and me, to bed.