WinPkgScan.psm1

# WinPkgScan module.
#
# Each command runs the WinPkgScan script of the same name that ships in
# this module folder, so it behaves exactly like the script in the release
# zip, with the same options and help:
#
# Find-OrphanedAppxFolders scan (read-only)
# Remove-OrphanedAppxFolders remove the folders marked in the CSV
# Restore-OrphanedAppxFolders put removed folders back from the Recycle Bin
# Install-WinPkgScan add Start menu and desktop shortcuts to the app
# Start-WinPkgScanApp open the app
#
# Get-Help <command> -Full shows every option.

$ModuleFolder = $PSScriptRoot

$ScriptCommands = [ordered]@{
    'Find-OrphanedAppxFolders'    = 'Find-OrphanedAppxFolders.ps1'
    'Remove-OrphanedAppxFolders'  = 'Remove-OrphanedAppxFolders.ps1'
    'Restore-OrphanedAppxFolders' = 'Restore-OrphanedAppxFolders.ps1'
    'Install-WinPkgScan'          = 'Install.ps1'
}

# Build a small function for each script, with the script's own help and
# parameters, that runs the script. Running it as a script (not as the
# function body) keeps "exit" inside the script, so it never closes your
# PowerShell window.
foreach ($commandName in $ScriptCommands.Keys) {
    $scriptPath = Join-Path $ModuleFolder $ScriptCommands[$commandName]
    $tokens = $null
    $parseErrors = $null
    $ast = [System.Management.Automation.Language.Parser]::ParseFile($scriptPath, [ref]$tokens, [ref]$parseErrors)
    if ($parseErrors) { throw "WinPkgScan: $scriptPath could not be read: $($parseErrors[0].Message)" }
    $helpText  = @($tokens | Where-Object { $_.Kind -eq 'Comment' -and $_.Text.StartsWith('<#') } | Select-Object -First 1)[0].Text
    $attribute = (@($ast.ParamBlock.Attributes) | ForEach-Object { $_.Extent.Text }) -join "`n"
    $definition = @(
        "function $commandName {"
        $helpText
        $attribute
        $ast.ParamBlock.Extent.Text
        "& '$($scriptPath.Replace("'", "''"))' @PSBoundParameters"
        '}'
    ) -join "`n"
    . ([scriptblock]::Create($definition))
}

function Start-WinPkgScanApp {
    <#
    .SYNOPSIS
        Opens the WinPkgScan app in its own window.
 
    .DESCRIPTION
        Starts WinPkgScan-App.ps1 in a new Windows PowerShell process, so this
        prompt stays free. Needs Windows.
 
    .EXAMPLE
        Start-WinPkgScanApp
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    param()
    $app = Join-Path $ModuleFolder 'WinPkgScan-App.ps1'
    $exe = 'powershell.exe'
    if ($env:SystemRoot) {
        $full = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
        if (Test-Path -LiteralPath $full) { $exe = $full }
    }
    if ($PSCmdlet.ShouldProcess($app, 'Start the WinPkgScan app')) {
        Start-Process -FilePath $exe -WindowStyle Hidden -ArgumentList (
            '-NoProfile -ExecutionPolicy Bypass -STA -WindowStyle Hidden -File "' + $app + '"')
    }
}

Export-ModuleMember -Function @(@($ScriptCommands.Keys) + 'Start-WinPkgScanApp')