Using PowerShell Modules and External Code in Dashboards

Product: PowerShell Universal
Version: 3.3.6

This issue originated from New-UDSelect OnChange Scope Broken · Issue #1305 · ironmansoftware/issues · GitHub

Using ones own PowerShell Modules, that maintain their own internal state, with Universal Dashboard is problematic because different parts of UD can run in separate runspaces, to quote Adam:

Variables that we move between runspaces are effectively readonly. Once the runspace is done executing, we reset its state of the runspace so nothing is retained in the local scope. You’d have to use session variables…

Unfortunately, if a PowerShell Module is using it’s own $script: scope then it won’t work between different ScriptBlocks used by UD Cmdlets and we can’t use the $Session: scope inside a Module without significant clunky re-writing of modules which would mean maintaining UD and non-UD variants of the same Module.

I even tried to get around the problem by ripping out the Cmdlets from the PowerShell Module, rewriting them to use the $Session: scope instead and then dotsourcing it into the Dashboard but that didn’t appear to work either. My only working solution so-far has been to do the aforementioned and then include the Cmdlet definitions in the Dashboard ps1 file itself but this isn’t scalable.

What is the best practice here? I don’t see how people are developing anything more than basic dashboards with these limitations or using PowerShell Modules that don’t maintain their own internal state.
@adam

Viaj, I haven’t done any real testing with this, but this may help in the meantime?

Function ConvertTo-UDModuleDotSource {
    Param (
        $ModulePath
    )
    $script = Get-Content -Path "$ModulePath" -Raw
    $NewScript = $script.replace('$Script:', '$Session:')
    $scriptblock = [scriptblock]::Create($Newscript)
    Return $scriptblock
}

. (ConvertTo-UDModule -ModulePath C:\FakePath\MyModule.psm1)
. (ConvertTo-UDModule -ModulePath C:\FakePath\MyScript.ps1)

Note: I misread your message at first, and had already written the first function.

Do you use a build script for the module? Perhaps you could output 2 modules, one “normal” and a “UD” version as a bandaid… The above logic should help you get there.

You could also add in something like this to your functions…

Function Do-Stuff {
    If ($Host.Name -eq "UDHost") {
        $Script:MyVar = $Session:MyVar
    }
    ## Does the stuff

    If ($Host.Name -eq "UDHost") {
        $Session:MyVar = $Script:MyVar
    }
}

Or…

Function Do-Stuff {
    Param (
        [switch]$UD
    )
    If ($UD -eq $true) {
        $Script:MyVar = $Session:MyVar
    }
    ## Does the stuff

    If ($UD -eq $true) {
        $Session:MyVar = $Script:MyVar
    }
}

I don’t see how people are developing anything more than basic dashboards with these limitations

Not needing to hardcode states, using SQL…

using PowerShell Modules that don’t maintain their own internal state.

MsGraph module works fine in PSU, though I’m not exactly sure how they maintain their state.

I have already tried your Do-Stuff approach, $Session: scope is not natively available in an imported PowerShell Module, ideally, I’d rather not have to rewrite the PowerShell modules to be UD-aware at all.

Not needing to hardcode states, using SQL…

I’m not sure I understand, are you saying not to use variables in the respective Modules runspace, such as $script: scope, and outsource state to an external system like SQL? Seems like a lot of overhead, even if I were to use something like Redis instead, not to mention connection details to said external system would have to live somewhere, bringing us back to the original problem.

MsGraph module works fine in PSU, though I’m not exactly sure how they maintain their state.

I’ve done some reverse engineering of the Microsoft Graph PowerShell Module in the past, it is mostly .NET libraries built from the PowerShell SDK. I could take the same approach but that would require rewriting my PowerShell modules from scratch.

ConvertTo-UDModuleDotSource is an interesting idea, I think I might try that moving forward, although it does feel hacky.

@Jori Do you use any of these techniques yourself or are these just suggestions?

It works for me. I modified a module on my system to include a function with this scriptblock…

Get-PSDrive | ConvertTo-JsonEx | Out-File C:\temp\ModPSDrive.json
Get-PSProvider | ConvertTo-JsonEx | Out-File C:\temp\ModPSProvider.json

image

This is on a non-persistent pwsh 7.2.6 environment, if you are running something different.

Dot Sourcing and putting important stateful variables to $Session: should fix your issue though. Since it’s not loading the module, variables are loaded into the parent scope, as opposed to it’s own child scope…

Just a suggestion :slight_smile: