Use PowerShell to make QR Codes

Since Elon Musk has decided to have a complete mantrum about posting links to Mastodon or any other social media, I thought I’d talk about a fun workaround: using QR Codes to post links.

As it turns out, doing so with PowerShell is really trivial:

  1. In a PowerShell window (on any platform you can run PowerShell on, this is not Windows-only at all), install QRCodeGenerator: Install-Module -Name QRCodeGenerator
  2. Once that’s installed, you can import the module for your session, although installing it in a root PowerShell session makes it available to everyone. To import: Import-Module -Name QRCodeGenerator
  3. There’s a few commands available, but really, the basic New-QRCodeText will work well for this: New-QRCodeText -Text “<URL you want to encode>” -OutPath <pathtofile.png> There’s an optional -Show parameter if you want to see the QR code before sending it.

That’s it. You can integrate that into any script you want that can call pwsh in the shell, so bash, AppleScript, whichever you prefer. So in five minutes, you can piss off an overly hemotional manbaby billionaire, and really, isn’t that what automation is for?

Advertisement

Get-Macinfo Update

tl;dr, updated for Apple Silicon

During my talk at JNUC, a few folks pointed out that my Get-Macinfo script didn’t work well on Apple Silicon. I wasn’t surprised, but as I don’t have an Apple Silicon Mac, I can’t exactly test for that. However, some of y’all really came through with details on command results, and with the help of folks, in particular Kelly Dickson and Dr. Michael Richmond, I was able to get the info I needed.

For Apple Silicon, in the system profiler hardware report, the following values:

  • CPU Speed
  • CPU Count
  • L2 Cache
  • L3 Cache
  • Hyperthreading

don’t exist. Not a shock, but as that query is dumped into an array, missing 5 items meant my array references were all wrong.

I’ve got the first update for Apple Silicon up at ye olde github site, so anyone with an Apple Silicon Mac who wants to look at it and feels like installing/running PowerShell on their Mac (if they don’t already have it) can beat on it. It still seems to work correctly on Intel.

Again, thanks to everyone who helped out, it’s really appreciated, and if anyone has anything they’d like to see added to the list of things Get-Macinfo reports on, I’m happy to add where I can.

Thanks!

Azure Management tip for PowerShell on MacOS

The other day I was messaging back and forth with a good friend and former minion who was talking about a roadblock he’d hit with trying to use PowerShell on a Mac to manage Azure servers. We talked a bit, and then I went hunting and stumbled on a way to do this, so I thought I should share it with you.

In this specific case, he was trying to manage his Exchange instance, and when he’d run Connect-ExchangeOnline, he’d get the web auth dialog, authenticated, and then get the following error:

Exception: This parameter set requires WSMan, and no supported WSMan client library was found. WSMan is either not installed, or unavailable for this system.

I tried it myself and got the same error, so I started poking. A bit of searching on the PowerShell Gallery led me to PSWSMan, and the docs for that helped me then run Install-WSMan (part of this for the Mac uses MacPorts, fyi.) You have to do the install as root, but once you’ve installed the modules and enabled, them, then you should be able to do many Azure things via PowerShell from your Mac.

If you’re ever trying to find a PowerShell module, I cannot recommend PowerShell Gallery enough as a starting point, it’s an amazing resource.

Fun with Get-Command

So when doing shell things in macOS, (or Linux for that matter), you use things like “which” and “locate” a lot. They’re handy. Well, PowerShell, unsurprisingly has its own version, Get-Command, (Full syntax and examples at the Get-Command documentation page.)

There’s a lot there, but I wanted to talk about just two cases, getting a single command, and getting a list of similar commands. So if you just run Get-Command on a single command, i.e. Get-Command pwsh, you get:

single command results

So this looks nice, but it’s functional. Let’s redo that command, but assign it to a variable: $thePwshCommand = Get-Command pwsh

If we just run the variable name, $thePwshCommand, you see the same thing as you would for Get-Command pwsh. So like any generic string, but with some nicer display formatting right? Well, no. As it turns out, the results of Get-Command are not just a string as shown by $thePwshCommand.GetType():

results of GetType()

So as we see, it’s not a string, but rather something called CommandInfo. What does this mean? Well, let’s say you don’t care about the type of executable a command is, or the version, you just want the path. No string parsing needed, just use $thePwshCommand.Source:

Et voila, no parsing of any kind needed

Since CommandInfo is a kind of object, you have some fun options for using what it returns without having to dink around with sed, grep, or what have you. The language and runtime take care of it for you.

But wait, there’s more. Now, as we can see, I’m running a preview version of PowerShell. Which means there’s a chance that there’s other versions of pwsh on my system. (By default, Get-Command returns the first hit it finds.) So how do we get all the versions of pwsh? Wildcards Get-Command pws*:

all the pws* things

so as we can see, there’s a few things. But what happens if we use a var for that? Well, it’s kind of neat…first the command, $allThingsPwsh = Get-Command pws*. If we just display the var, we get about what we expect for $allThingsPwsh:

No surprises here

So that’s another CommandInfo object right? Nope, as $allThingsPwsh.GetType() shows us:

Well that’s different

So when you get multiple items returned, it’s now an array, of CommandInfo objects. So if we want to see the second item, we get just that entry for $allThingsPwsh[1]:

Second item in the array

But as it’s an array of objects, we gets more flexibility, like if we just want to see the path for that entry, we use $allThingsPwsh[2].Source:
/usr/local/bin/pwsh-preview

If we just want the paths for all the items, we use $allThingsPwsh.Source, and we get:
/usr/local/microsoft/powershell/7-preview/pwsh
/usr/local/bin/pwsh
/usr/local/bin/pwsh-preview

If we just want the name and the path separated by a tab, easily done with
$allThingsPwsh[2].Name + "`t" +  $allThingsPwsh[2].Source:
pwsh-preview /usr/local/bin/pwsh-preview

So yeah, because PowerShell understands objects and arrays better than things like shell, you get a lot of flexibility in the command, saving you from a lot of the endless string/output parsing legerdemain you have to do with shell.

For more info on arrays:
https://docs.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-arrays?view=powershell-7.2 and https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_arrays?view=powershell-7.2

Enjoy!

Call PowerShell Commands from AppleScript/JXA

This will probably work within ASOC too, since do shell script works there. On macOS, the powershell “executable” is pwsh, usually in usr/local/bin/pwsh for the non-beta versions. If you run pwsh -h, you see that the pwsh utility is quite versatile, and by using the -c parameter, you can treat it like osascript, in that you can pass it a single command as a string, or an entire script block. You can also pass it a PowerShell script with the -f command. So to use this from within AppleScript/JXA, (AppleScript syntax shown), it looks like:

do shell script "/usr/local/bin/pwsh -c \"Get-PSDrive\" "

You can also use this with custom modules:

do shell script "/usr/local/bin/pwsh -c \"Get-MacInfo HardwareModelID\" "

So if you want to use a slightly more coherent shell environment like PowerShell from within AppleScript, you can.

From My Heart and From My Hands

It’s ALIVE!!!

So the script I talked about here is now an actual importable Poweshell Module. It wasn’t hard, but it was a pain in the ass, more than I think it should have been. Most of this is because, unsurprisingly, almost all the documentation on modules is highly windows-centric. So hopefully, this post will help that.

This is a very basic module. It exports no cmdlets, just a single function. Which means this post is not going to be a huge help for some gigantic thing, but it hopefully is a start.

The first thing you want to do is make sure you have a working script, aka a .ps1 file. Doesn’t have to be complicated, Get-MacInfo really isn’t. It just grabs a bunch of info, shoves it into a hashtable and shows you what it found. Once your basic code and logic is working, you’ll want to copy the .ps1 file to a .psm1 file. That’s important in the PowerShell world, as that’s the traditional extension for a PowerShell Module.

Next, wrap all your code that isn’t comment-based help in a function. In my case, it was just: function Get-MacInfo {<code>} Since it’s a single-function module, the only function is well, Get-MacInfo. If you’re going to have multiple functions, including setter functions, you really want to read the pertinent MS docs, starting here. Understanding those will help. Once you’ve wrapped it in a function, you want to export that function so it’s available to Powershell. To do that, add an Export-ModuleMember line to the bottom of your .psm1 file like so:

Export-ModuleMember -Function 'Get-MacInfo'

This is the only function I have, so I only need a simple line. If you’re exporting cmdlets or both functions and cmdlets…you’re probably well beyond what I know or can help with.

Next, we want to create the module manifest. This is fairly straightforward, but there’s some gotchas that can make you a bit bonkers. The basic creation is simple, you use the New-ModuleManifest. There’s a lot of parameters you can use, the only one that’s required is, I believe, the -Path variable. Mine looked like this:

New-ModuleManifest -Path 'Path for where you want the .psd1 file to be created' -ModuleVersion '1.0'-Author 'John C. Welch' -Company 'Bynkii.com' -Description 'A macOS version of the Get-ComputerInfo module' -ProjectUri 'https://github.com/johncwelch/Get-MacInfo' -ReleaseNotes 'First module release' -HelpInfoURI'https://github.com/johncwelch/Get-MacInfo/wiki' 

That will create a basic .psd1 file in the location that you specify in the -Path parameter, and you’re almost good to go.

Almost

So there’s a couple things you have to do manually. First, you have to actually tell the manifest what it’s referring to. If you open the manifest file (it’s basic XML), look for the #RootModule = ” line. MAKE SURE YOU UNCOMMENT IT. I didn’t and lost my fool mind for a few hours. Put the name of the .psm1 file in between the single quotes. Mine looks like:

RootModule = 'Get-MacInfo.psm1'

Next, look for the FunctionsToExport line, make sure IT is uncommented (it should be) and put in the name of the function(s) you’re exporting from the .psm1 file. Since I only have the one, mine looks like this:

FunctionsToExport = @('Get-MacInfo')

Everything else should be okay as is. Once you have those two files set up, you want to put them in the right place. You can put them anywhere, but putting them in the paths that PowerShell knows about makes your life much easier. On my machine, since I’m running the 7.1 preview, my paths (and the command to show them) look like this:

 $env:PSModulePath -split ‘:’                                                                                                                                                 ~/.local/share/powershell/Modules                                                                                                                                                  /usr/local/share/powershell/Modules
/usr/local/microsoft/powershell/7-preview/Modules                

If you only want to have the modules available to a single user, put them in the home directory Modules folder. For everyone on the machine, put them in one of the others. I have specific install instructions on the GitHub wiki for the project, so I won’t belabor those here.

If you put the module in the right paths, running Get-MacInfo will automatically import the module, and it will just work. If you put it somewhere else, you have to deal with manually managing Import-Module, and that’s on you.

Please note this is a really simple project. Modules can be really, really complicated and include binary files. This guide will be of no help at all for those. But if you’re just getting started, this may be of use. In any event, if you want to use Get-MacInfo, it’s on GitHub, with the install instructions,