Public/Unseal-SCOMMP.ps1
|
Function Unseal-SCOMMP { <# .SYNOPSIS Unseals SCOM Management Pack files (.mp, .mpb) to XML and extracts embedded resources. .DESCRIPTION Unseal-SCOMMP processes sealed Management Pack files (.mp) and Management Pack Bundles (.mpb), converting them to unsealed XML format and extracting any embedded resources (DLLs, images, SQL scripts, etc.). Each input file is placed into a version-coded subfolder in the output directory named FileName(Version). The function supports two input modes: - Directory mode (default): Specify -InputDir to process all .mp/.mpb files in a directory. - Pipeline mode: Pipe FileInfo objects (e.g., from Get-ChildItem) for selective processing. SDK assemblies are auto-discovered from the GAC, common SCOM install paths, registry, or an explicit -SdkPath parameter. .PARAMETER InputDir Path to the directory containing sealed .mp and/or .mpb files. Searches recursively. Alias: inDir (for backward compatibility with the original Unseal-SCOMMP). .PARAMETER InputObject A System.IO.FileInfo object received from the pipeline. Must have a .mp or .mpb extension; other file types are skipped with a warning. .PARAMETER OutputDir Path to the output directory where unsealed XML and resources will be written. Created automatically if it does not exist. Alias: outDir (for backward compatibility with the original Unseal-SCOMMP). .PARAMETER SdkPath Explicit path to the directory containing the SCOM SDK DLLs (Microsoft.EnterpriseManagement.Core.dll and Microsoft.EnterpriseManagement.Packaging.dll). If not specified, the function attempts auto-discovery via GAC, $env:SCOM_SDK_PATH, common SCOM install paths, and the registry. .PARAMETER Force When specified, overwrites existing output folders. Without this switch, files whose version-coded output folder already exists are skipped. .INPUTS System.IO.FileInfo — from Get-ChildItem or Extract-SCOMMSI -PassThru. .OUTPUTS None. Writes unsealed XML files and resources to the output directory. .EXAMPLE Unseal-SCOMMP -inDir C:\MyMPs -outDir C:\MyMPs\UnsealedMPs Unseals all .mp and .mpb files in C:\MyMPs using the legacy parameter names. .EXAMPLE Unseal-SCOMMP -InputDir 'C:\Program Files (x86)\System Center Management Packs' -OutputDir 'C:\Temp\Unsealed' Unseals all management packs from a System Center Management Pack directory. .EXAMPLE Get-ChildItem 'C:\MPs' -Filter '*.mpb' | Unseal-SCOMMP -OutputDir 'C:\Unsealed' Pipes only .mpb files into Unseal-SCOMMP for selective processing. .EXAMPLE Unseal-SCOMMP -InputDir 'C:\MPs\Sealed' -OutputDir 'C:\MPs\Unsealed' -Force Re-runs unsealing and overwrites any previously exported folders. .EXAMPLE Unseal-SCOMMP -InputDir 'C:\MPs' -OutputDir 'C:\Out' -SdkPath 'D:\SCOM\SDK' Uses SCOM SDK DLLs from a custom path when they are not in the GAC or standard locations. .EXAMPLE Extract-SCOMMSI -Path 'C:\Downloads\*.msi' -OutputDir 'C:\Extracted' -PassThru | Unseal-SCOMMP -OutputDir 'C:\Unsealed' End-to-end pipeline: extracts .mp/.mpb from MSI packages then unseals them to XML. .NOTES Author: Tyson Paul Blog: https://monitoringguys.com/ Requires: SCOM SDK DLLs (Microsoft.EnterpriseManagement.Core, Microsoft.EnterpriseManagement.Packaging) History: 2018.05.25 - Initial. 2019.05.01 - Fixed issue where output path would display extra backslash. 2025.07.14 - Major upgrade: CmdletBinding, pipeline input, SDK auto-discovery, -SdkPath, -Force, per-file error handling, Write-Host replaced with Write-Verbose. .LINK Extract-SCOMMSI Get-SCOMMPFileInfo Get-SCOMClassInfo #> [CmdletBinding(DefaultParameterSetName = 'ByDirectory')] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'ByDirectory')] [ValidateScript({ Test-Path $_ -PathType Container })] [Alias('inDir')] [string]$InputDir, [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'ByPipeline')] [System.IO.FileInfo]$InputObject, [Parameter(Mandatory, Position = 1)] [Alias('outDir')] [string]$OutputDir, [Parameter()] [string]$SdkPath, [Parameter()] [switch]$Force ) Begin { #region Internal helper: Load SDK DLLs function Initialize-SdkAssemblies { param([string]$ExplicitPath) $requiredDlls = @( 'Microsoft.EnterpriseManagement.Core', 'Microsoft.EnterpriseManagement.Packaging' ) # Attempt 1: GAC via LoadWithPartialName $allLoaded = $true foreach ($dll in $requiredDlls) { $asm = [Reflection.Assembly]::LoadWithPartialName($dll) if ($null -eq $asm) { $allLoaded = $false Write-Verbose "SDK DLL '$dll' not found in GAC." break } else { Write-Verbose "Loaded '$dll' from GAC: $($asm.Location)" } } if ($allLoaded) { return $true } # Attempt 2: Search explicit paths $searchPaths = @() if ($ExplicitPath) { $searchPaths += $ExplicitPath } if ($env:SCOM_SDK_PATH) { $searchPaths += $env:SCOM_SDK_PATH } # SCOM 2019+ paths $searchPaths += "${env:ProgramFiles}\Microsoft System Center\Operations Manager\Server\SDK Binaries" $searchPaths += "${env:ProgramFiles}\Microsoft System Center\Operations Manager\Console\SDK Binaries" # SCOM 2016 paths $searchPaths += "${env:ProgramFiles}\Microsoft System Center Operations Manager\Server\SDK Binaries" $searchPaths += "${env:ProgramFiles}\Microsoft System Center Operations Manager\Console\SDK Binaries" # Attempt 3: Registry probe $regKey = 'HKLM:\SOFTWARE\Microsoft\System Center Operations Manager\12\Setup' if (Test-Path $regKey) { $installDir = (Get-ItemProperty -Path $regKey -ErrorAction SilentlyContinue).InstallDirectory if ($installDir) { $searchPaths += (Join-Path $installDir 'SDK Binaries') $searchPaths += $installDir } } foreach ($searchDir in $searchPaths) { if (-not (Test-Path $searchDir)) { continue } Write-Verbose "Searching for SDK DLLs in: $searchDir" $foundAll = $true foreach ($dll in $requiredDlls) { $dllFile = Join-Path $searchDir "$dll.dll" if (Test-Path $dllFile) { try { [Reflection.Assembly]::LoadFrom($dllFile) | Out-Null Write-Verbose "Loaded '$dll' from: $dllFile" } catch { Write-Verbose "Failed to load '$dllFile': $_" $foundAll = $false break } } else { $foundAll = $false break } } if ($foundAll) { return $true } } # All attempts failed Write-Error @" SCOM SDK DLLs could not be found. The following assemblies are required: - Microsoft.EnterpriseManagement.Core.dll - Microsoft.EnterpriseManagement.Packaging.dll To resolve, try one of: 1. Run this function on a machine with SCOM Console or Server installed. 2. Specify the -SdkPath parameter pointing to the directory containing the DLLs. 3. Set the SCOM_SDK_PATH environment variable to the SDK Binaries directory. Common locations: - C:\Program Files\Microsoft System Center\Operations Manager\Server\SDK Binaries - C:\Program Files\Microsoft System Center\Operations Manager\Console\SDK Binaries "@ return $false } #endregion # Create output directory if needed if (-not (Test-Path $OutputDir)) { Write-Verbose "Creating output directory: $OutputDir" New-Item -Path $OutputDir -ItemType Directory -Force | Out-Null } # Load SDK $sdkLoaded = Initialize-SdkAssemblies -ExplicitPath $SdkPath if (-not $sdkLoaded) { return } $allFailed = @() $processedCount = 0 # Collect files for directory mode in Begin so Process handles pipeline $filesToProcess = @() if ($PSCmdlet.ParameterSetName -eq 'ByDirectory') { $filesToProcess = @(Get-ChildItem $InputDir -Include '*.mp','*.mpb' -Recurse -File -ErrorAction SilentlyContinue) if ($filesToProcess.Count -eq 0) { Write-Warning "No .mp or .mpb files found in '$InputDir'." return } Write-Verbose "Found $($filesToProcess.Count) file(s) to process in '$InputDir'." } } Process { # Determine which files to process this iteration if ($PSCmdlet.ParameterSetName -eq 'ByPipeline') { if ($InputObject.Extension -notin @('.mp', '.mpb')) { Write-Warning "Skipping '$($InputObject.Name)' - not a .mp or .mpb file." return } $filesToProcess = @($InputObject) } foreach ($file in $filesToProcess) { $processedCount++ try { if ($file.Extension -eq '.mp') { # Process .mp file Write-Verbose "Processing MP: $($file.FullName)" $sourceDir = Split-Path $file.FullName -Parent $mpStore = New-Object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackFileStore($sourceDir) $mp = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPack($file.FullName) $versionStr = 'unknown' try { $versionStr = $mp.Version.ToString() } catch {} $folderName = "$($file.Name)($versionStr)" $targetPath = Join-Path $OutputDir $folderName if ((Test-Path $targetPath) -and -not $Force) { Write-Verbose "Skipping '$($file.Name)' - output folder exists. Use -Force to overwrite." continue } if (Test-Path $targetPath) { Remove-Item $targetPath -Recurse -Force } New-Item -Path $targetPath -ItemType Directory -Force | Out-Null $mpWriter = New-Object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter($targetPath) [void]$mpWriter.WriteManagementPack($mp) Write-Verbose " -> Exported to: $targetPath" } elseif ($file.Extension -eq '.mpb') { # Process .mpb file Write-Verbose "Processing MPB: $($file.FullName)" $sourceDir = Split-Path $file.FullName -Parent $mpStore = New-Object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackFileStore($sourceDir) $mpbReader = [Microsoft.EnterpriseManagement.Packaging.ManagementPackBundleFactory]::CreateBundleReader() $bundle = $mpbReader.Read($file.FullName, $mpStore) $versionStr = 'unknown' try { $firstMp = $bundle.ManagementPacks | Select-Object -First 1 if ($firstMp -and $firstMp.Version) { $versionStr = $firstMp.Version.ToString() } } catch {} $folderName = "$($file.Name)($versionStr)" $targetPath = Join-Path $OutputDir $folderName if ((Test-Path $targetPath) -and -not $Force) { Write-Verbose "Skipping '$($file.Name)' - output folder exists. Use -Force to overwrite." continue } if (Test-Path $targetPath) { Remove-Item $targetPath -Recurse -Force } New-Item -Path $targetPath -ItemType Directory -Force | Out-Null $mpWriter = New-Object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter($targetPath) foreach ($bundleItem in $bundle.ManagementPacks) { [void]$mpWriter.WriteManagementPack($bundleItem) $streams = $bundle.GetStreams($bundleItem) foreach ($stream in $streams.Keys) { $mpElement = $bundleItem.FindManagementPackElementByName($stream) $fileName = $mpElement.FileName if ($null -eq $fileName) { $outpath = Join-Path $targetPath ('.' + $stream + '.bin') } elseif ($fileName.IndexOf('\') -gt 0) { $outpath = Join-Path $targetPath (Split-Path $fileName -Leaf) } else { $outpath = Join-Path $targetPath $fileName } Write-Verbose " -> Resource: $outpath" $fs = [System.IO.File]::Create($outpath) $streams[$stream].WriteTo($fs) $fs.Close() } } Write-Verbose " -> Exported to: $targetPath" } } catch { Write-Warning "Failed to process '$($file.Name)': $_" $allFailed += $file.Name } } # Reset for next pipeline iteration if ($PSCmdlet.ParameterSetName -eq 'ByPipeline') { $filesToProcess = @() } } End { $successCount = $processedCount - $allFailed.Count if ($processedCount -gt 0) { Write-Verbose "Processing complete. $successCount of $processedCount file(s) succeeded." } if ($allFailed.Count -gt 0) { Write-Warning "The following $($allFailed.Count) file(s) failed to process:" $allFailed | ForEach-Object { Write-Warning " - $_" } } } } |