Using XAML in PowerShell and .NET apps in PowerShell Modules

Recently, in my “real” job, I wanted to improve on the process of adding applications into SCCM/Software Center. While the SCCM wizard’s not bad, it’s kind of tedious, and you can’t save any of the settings for an app easily. That was something I wanted to do, because SCCM has a solid, well-documented (APPLE PAY ATTENTION) PowerShell automation implementation. So I realized that if I could save the specific application settings for an application package in a Json file, then updates/new version deployments get easier right? Take 1.X Json file, update it for 2.X, point script at Json file and bang, done.

So there were a couple things I needed/wanted. First, I wanted this to be a PowerShell module, because that would simplify things. Second, I wanted to have an app that would make creating the Json file easier, which meant a proper GUI and on Windows that meant .NET. Okay, so cool, that’s not too bad. But then I realized I need two more things:

  • I needed a way to get the path to the Json file the .NET app created to the PowerShell script without nonsense like saving to temp files
  • I needed to have a dialog in the PowerShell app that allowed for three options if you ran the module without providing a path to a Json file: Point to an existing file, Create a new file, or Cancel
  • The second one turned out the be the easiest, because if you’re doing a generic .NET app, you can actually integrate XAML code directly into a PowerShell Script. Not .NET Maui, that doesn’t work for…some reason, but “old skool” XAML does.

    Using XAML in PowerShell

    This is pretty easy. First, figure out the UI you want the XAML to build. In my case, it was just a three-button dialog, with everything fitting neatly between <window></window> tags, about 13 lines in total. You wrap all tat in a [XML] tag in PowerShell so:

    [xml]$xamlVarName = @" <Window> XAML here </Window> "@

    That creates the XAML doc you need. From there we set up our window:

    $reader=(New-Object System.Xml.XmlNodeReader $xamlVarName) $Window=[Windows.Markup.XamlReader]::Load($reader)

    Then we get the results of the button clicks. First, we assign PowerShell vars to our XAML buttons

    $findExistingButton = $Window.FindName("findExisting") $createNewButton = $Window.FindName("createNew") $cancelButton = $Window.FindName("cancel")

    Then we actually get the results of the clicks. By using the same global var, the last clicked button wins:

    $findExistingButton.Add_Click({ $Global:buttonClickedText = $findExistingButton.Content $Window.Close() }) $createNewButton.Add_Click({ $Global:buttonClickedText = $createNewButton.Content $Window.Close() }) $cancelButton.Add_Click({ $Global:buttonClickedText = $cancelButton.Content })

    Because of how the cancel button works in XAML, no need for a specific window close statement

    Finally we display the window, and return the button clicked text to the calling function

    $Window.ShowDialog()|Out-Null return $buttonClickedText

    That’s really all their is, but it let me build a dialog with three buttons fairly easily and in about 29 lines of code for the entire function. Not bad at all.

    Next was getting the path for the Json file the .NET app had to create. That app was kind of “fun” because of things like dealing with how Windows can have a thing that looks like a .ico file but is really a compressed PNG file lying to you. Not only will the PowerShell scripts totally not work with that, but if you try to add one manually in the SCCM wizard, it crashes the configuration manger console application. Fun times, especially because there’s no easy way to tell if a .ico file is secretly a PNG file other than reading the magic number from the file. FUn times. Also, Windows’ icon file sizes, at least for SCCM max out at 512×512 pixels, so kinda tiny. Icons are a place Microsoft could really learn from Apple on.

    Anyway, it turns out the way to get the json file path was simple enough that it felt like cheating. In the .NET app, once I saved the json file, and I had the path for that, all I had to do was Console.Write(jsonFilePath);, exit the app, and done. (Console.Write(), not Console.WriteLine() which adds a newline to the end that makes PowerShell sad when trying to use that as a path string.)

    Then in the PowerShell code, I have a function that I pass the path to the .NET app to as a mandatory parameter. The code to fire up the app and get that one line of output is:

    $ps1 = [System.Diagnostics.ProcessStartInfo]::new($pathToDotNetExecutable) $ps1.UseShellExectute = $false $ps1.RedirectStandardOutput = $true $ps1Process = [System.Diagnostics.Process]::Start($ps1) $processOutput = $ps1Process.StandardOutput.ReadToEnd() return $processOutput

    The ReadToEnd() waits for the .NET app to quit, so you have to have that in there somewhere or you never get the output. $processOutput is the result of the Console.Write(jsonFilePath); statement in the .NET app, and it returns that so the script can use it to build the SCCM application package, application package deployment type, and the application package install detection method.

    Unfortunately, this is a Windows-only trick both for the XAML and for the Configuration Manager PowerShell, since that application is Windows-only. But still, kind of handy, and now I have an easy way to get info out of a gui into a calling PowerShell script.

    #NET #NETAppToPowerShell #PowerShell #PowerShellXAMLIntegration #SCCM #XAML