Import-Module (Import-PSSession ....) - where does the format data go?

Hello, I am seeking help and would like to continue on from a previous post (https://powershell.org/forums/topic/using-import-module-global-in-a-powershell-module/).

The main point to carryover from that thread is that Import-Module -Global will make things available after the scope from wherever that was done goes away.

I’ve found that I have to import PS sessions this way if I’m going to include the parent function in another module. The issue I’m hitting is that where Import-PSSession downloads commands and their format data (which can be narrowed down with the -CommandName and -FormatTypeName parameters), Import-Module doesn’t save the format data.

This wouldn’t be an issue if the imported module was intended for use in a script, but when the intended use is the console, that format info is pretty handy, especially for cmdlets like Get-Mailbox which has a long list of properties that will output if no format data says otherwise.

Has anyone else encountered this and found a way to get that format data to be available to the globally scoped imported module?

I ended up figuring out my issue, but didn’t want to alter the original problem description in case it ends up being useful to anyone.

I was getting in my own way by having imported all 3 modules in my function (within my module) as -Global, but also had logic to only do this if a particular one of those modules wasn’t detected with Get-Module. So successive runs of the function would end up bypassing a proper re-import of all 3 modules that are required. I needed to import my first two modules as local (without -Global), then only import the 3rd as global (-Global) as it was the only one I wanted to have available after the function completes.

For context the script / function I’m working on is my Connect-Exchange function, within my conex module. It connects to either Exchange on-premises or Online, with choice prompts and parameters to accommodate most scenarios. The modules within, that I’m importing, are the Exchange Online PowerShell module’s DLL and CreateExoPSSession.ps1). My first snag was needing to import the PS sessions using Import-Module -Global. But upon figuring that out, I needlessly applied the same logic to two sub-modules that weren’t actually necessary to be global. Then I finished it off with a self-defeating only if statement. PEBKAC.

It’s all in Connect-Exchange.ps1 / conex.psm1

Thanks @jeremytbradshaw for sharing your scenario with solution, this will be useful for someone in future for sure.

Thanks @kvprasoon ! And talk about generating egg for my own face, but now I have flip-flopped back to my original strategy of needing all 3 modules to be imported globally. What the correct fix for me ended up being is to check for and remove any of the 3 modules before anything else then import them freshly. This proper cleanup, and -AllowClobber (just in case; destructive I know, but intentional in this case), allows re-runs of the function to work like the initial run, without collisions of commands (which was the specific cause of my missing format data re: my initial post).

As a final closing to this chapter:), my code is open for anyone’s review/reference:

https://github.com/JeremyTBradshaw/PowerShell/blob/master/.Modules/conex/conex.psd1

https://www.powershellgallery.com/packages/conex/0.0.0.1

A few of the key points involved in making the imported modules available to the console, particularly when Connect-Exchange lives inside a module of its own (conex.psm1):

  • The EXO PS binary module (the DLL) and CreateExoPSSession.ps1 don't come with standard module manifests (.psd1's).
    • Solution: On the fly, I create a PSD1 for each of them, dropping them into the same folder as the official EXO PS module.
  • For the EXO PS binary module (the DLL), the manifest's ModuleVersion needs to match the module's, or the commands won't be available to the console, EVEN though they show in the ExportedCmdlets list when you run Get-Module Microsoft.Exchange.Management.ExoPowerShellModule.dll.
    • Solution: Import the DLL module once. Then, pull its version # while creating the on-the-fly manifest (.psd1). THEN RE-IMPORT the DLL module again, this time while a proper manifest exists. This makes those exported cmdlets actually available in the console after the function completes. This will also ensure that the manifest version always matches the latest EXO PS module dll version (until the MS Exchange team reinvent their module of course).
  • In the end, I did need to make all 3 modules involved, globally available (-Global). The 3 modules are - the EXO PS binary module (dll), CreateExoPSSession.ps1, and the Import-Module (Import-PSSession ....).
    • This was necessary to ensure a smooth and seamlesss handling of subsequent runs of the function, as well as re-establishing a connected session after re-authentication or idle time cause the original session to break.
These solutions can be seen in the following block:
switch ($ExchangeConnectionChoice) {
1 {
    try {
        $ExoPSModuleSearchProperties = @{

            Path        = "$($env:LOCALAPPDATA)\Apps\2.0\"
            Recurse     = $true
            ErrorAction = 'Stop'
        }

        $ExoPSModule =  Get-ChildItem @ExoPSModuleSearchProperties -Filter 'Microsoft.Exchange.Management.ExoPowerShellModule.dll' |
                        Where-Object {$_.FullName -notmatch '_none_'} |
                        Sort-Object LastWriteTime |
                        Select-Object -Last 1

        Import-Module $ExoPSModule.FullName -ErrorAction:Stop

        $ExoPSModuleManifest = $ExoPSModule.FullName -replace '\.dll','.psd1'

        $NewExoPSModuleManifestProps = @{

                Path            = $ExoPSModuleManifest
                RootModule      = $ExoPSModule.Name
                ModuleVersion   = "$((Get-Module $ExoPSModule.FullName -ListAvailable).Version.ToString())"
                Author          = 'Jeremy Bradshaw (https://github.com/JeremyTBradshaw)'
                CompanyName     = 'jb365'
        }

        New-ModuleManifest @NewExoPSModuleManifestProps

        Import-Module $ExoPSModule.FullName -Global -ErrorAction:Stop

        $CreateExoPSSessionPs1 = Get-ChildItem -Path $ExoPSModule.PSParentPath -Filter 'CreateExoPSSession.ps1'

        $CreateExoPSSessionManifest = $CreateExoPSSessionPs1.FullName -replace '\.ps1','.psd1'

        $CreateExoPSSessionPs1 =    $CreateExoPSSessionPs1 |
                                    Get-Content |
                                    Where-Object {-not ($_ -like 'Write-Host*')}

        $CreateExoPSSessionPs1 -join "`n" |
        Set-Content -Path "$($CreateExoPSSessionManifest -replace '\.psd1','.psm1')"

        $NewCreateExoPSSessionManifest = @{

                Path            = $CreateExoPSSessionManifest
                RootModule      = Split-Path -Path ($CreateExoPSSessionManifest -replace '\.psd1','.psm1') -Leaf
                ModuleVersion   = '1.0'
                Author          = 'Jeremy Bradshaw (https://github.com/JeremyTBradshaw)'
                CompanyName     = 'jb365'
        }

        New-ModuleManifest @NewCreateExoPSSessionManifest

        Import-Module "$($ExoPSModule.PSParentPath)\CreateExoPSSession.psm1" -Global -ErrorAction:Stop
    }</pre>

…and shortly after the above:

Import-Module (Import-PSSession $ExoPSSession @ImportPSSessionProps) -Global -DisableNameChecking -ErrorAction:Stop