completers/dism_completer/dism_completer.ps1

# DISM tab completion for PowerShell
# Static catalog of the documented DISM surface, enriched from DISM's own help when the shell is elevated
# (DISM refuses to print help otherwise).
# Usage: . .\dism_completer.ps1

Set-StrictMode -Version 2.0

if (-not (Get-Variable -Name DismCompletionCatalog -Scope Script -ErrorAction Ignore)) {
    $script:DismCompletionCatalog = @{
        Initialized       = $false
        Commands          = @()
        GlobalSwitches    = @()
        OptionSpecs       = $null
        LiveCommandKeys   = @{}
        ExtraSwitchesByKey = @{}
    }
}

function New-DismSwitchSpec {
    param(
        [string]$Token,
        [string]$Description,
        [string]$Kind = 'Flag',
        [string[]]$Values = @(),
        [string[]]$Extensions = @(),
        [string]$Placeholder = '<value>'
    )

    [pscustomobject]@{
        Token       = $Token
        Description = $Description
        Kind        = $Kind
        Values      = @($Values)
        Extensions  = @($Extensions)
        Placeholder = $Placeholder
    }
}

function New-DismCommandSpec {
    param(
        [string]$Token,
        [string]$Description,
        [object[]]$Switches = @(),
        [string]$Kind = 'Flag',
        [string[]]$Values = @(),
        [string]$Placeholder = '<value>'
    )

    [pscustomobject]@{
        Token       = $Token
        Description = $Description
        Kind        = $Kind
        Values      = @($Values)
        Extensions  = @()
        Placeholder = $Placeholder
        Switches    = @($Switches)
    }
}

function Get-DismStaticCatalog {
    $imageExtensions = @('.wim', '.esd', '.swm', '.ffu', '.vhd', '.vhdx')
    $s = @{
        ImageFile            = New-DismSwitchSpec -Token '/ImageFile:' -Description 'Path to the image file (.wim, .esd, .ffu, .vhd, .vhdx).' -Kind 'File' -Extensions $imageExtensions
        SourceImageFile      = New-DismSwitchSpec -Token '/SourceImageFile:' -Description 'Path to the source image file.' -Kind 'File' -Extensions $imageExtensions
        DestinationImageFile = New-DismSwitchSpec -Token '/DestinationImageFile:' -Description 'Path to the destination image file.' -Kind 'File' -Extensions $imageExtensions
        SWMFile              = New-DismSwitchSpec -Token '/SWMFile:' -Description 'Naming pattern and location of split .swm files.' -Kind 'File' -Extensions @('.swm')
        SFUFile              = New-DismSwitchSpec -Token '/SFUFile:' -Description 'Naming pattern and location of split .sfu files.' -Kind 'File' -Extensions @('.sfu')
        Index                = New-DismSwitchSpec -Token '/Index:' -Description 'Index number of the image inside the file (use 1 for FFU and VHD).' -Kind 'Value' -Placeholder '<index>'
        SourceIndex          = New-DismSwitchSpec -Token '/SourceIndex:' -Description 'Index number of the source image.' -Kind 'Value' -Placeholder '<index>'
        Name                 = New-DismSwitchSpec -Token '/Name:' -Description 'Name of the image.' -Kind 'Value' -Placeholder '<image-name>'
        SourceName           = New-DismSwitchSpec -Token '/SourceName:' -Description 'Name of the source image.' -Kind 'Value' -Placeholder '<image-name>'
        DestinationName      = New-DismSwitchSpec -Token '/DestinationName:' -Description 'Name of the destination image.' -Kind 'Value' -Placeholder '<image-name>'
        Description          = New-DismSwitchSpec -Token '/Description:' -Description 'Description of the image.' -Kind 'Value' -Placeholder '<description>'
        MountDir             = New-DismSwitchSpec -Token '/MountDir:' -Description 'Directory the image is (or will be) mounted to.' -Kind 'Directory'
        ApplyDir             = New-DismSwitchSpec -Token '/ApplyDir:' -Description 'Target directory the image is applied to.' -Kind 'Directory'
        CaptureDir           = New-DismSwitchSpec -Token '/CaptureDir:' -Description 'Source directory to capture.' -Kind 'Directory'
        ConfigFile           = New-DismSwitchSpec -Token '/ConfigFile:' -Description 'Configuration file listing capture and compress exclusions.' -Kind 'File' -Extensions @('.ini')
        CheckIntegrity       = New-DismSwitchSpec -Token '/CheckIntegrity' -Description 'Detects and tracks .wim file corruption.'
        Verify               = New-DismSwitchSpec -Token '/Verify' -Description 'Checks for errors and file duplication.'
        NoRpFix              = New-DismSwitchSpec -Token '/NoRpFix' -Description 'Disables the reparse point tag fix.'
        Bootable             = New-DismSwitchSpec -Token '/Bootable' -Description 'Marks a volume image as bootable (WinPE images only).'
        WIMBoot              = New-DismSwitchSpec -Token '/WIMBoot' -Description 'WIMBoot configuration (Windows 8.1 images only).'
        EA                   = New-DismSwitchSpec -Token '/EA' -Description 'Captures or applies extended attributes.'
        Compress             = New-DismSwitchSpec -Token '/Compress:' -Description 'Compression type for the capture or export.' -Kind 'Enum' -Values @('max', 'fast', 'none', 'recovery')
        FileSize             = New-DismSwitchSpec -Token '/FileSize:' -Description 'Maximum size in MB of each split file.' -Kind 'Value' -Placeholder '<MB-size>'
        PackagePath          = New-DismSwitchSpec -Token '/PackagePath:' -Description 'Path to a .cab/.msu package or a folder of packages.' -Kind 'File' -Extensions @('.cab', '.msu', '.appx', '.appxbundle', '.msix', '.msixbundle', '.spp')
        PackageName          = New-DismSwitchSpec -Token '/PackageName:' -Description 'Name of the package as listed in the image.' -Kind 'Value' -Placeholder '<package-name>'
        FeatureName          = New-DismSwitchSpec -Token '/FeatureName:' -Description 'Name of the feature as listed by /Get-Features.' -Kind 'Value' -Placeholder '<feature-name>'
        CapabilityName       = New-DismSwitchSpec -Token '/CapabilityName:' -Description 'Name of the capability as listed by /Get-Capabilities.' -Kind 'Value' -Placeholder '<capability-name>'
        Source               = New-DismSwitchSpec -Token '/Source:' -Description 'Location of the files required to restore or repair (for example a mounted Windows folder).' -Kind 'Directory'
        LimitAccess          = New-DismSwitchSpec -Token '/LimitAccess' -Description 'Prevents DISM from contacting Windows Update.'
        Format               = New-DismSwitchSpec -Token '/Format:' -Description 'Report output format.' -Kind 'Enum' -Values @('Table', 'List')
        Driver               = New-DismSwitchSpec -Token '/Driver:' -Description 'Path to a driver .inf file or a folder of drivers.' -Kind 'File' -Extensions @('.inf')
        Distribution         = New-DismSwitchSpec -Token '/Distribution:' -Description 'Path to the distribution share.' -Kind 'Directory'
        CustomDataPath       = New-DismSwitchSpec -Token '/CustomDataPath:' -Description 'Custom data file added to the app package as Custom.dat.' -Kind 'File'
    }

    $globals = @(
        New-DismSwitchSpec -Token '/?' -Description 'Displays information about available DISM command-line options and arguments.'
        New-DismSwitchSpec -Token '/Get-Help' -Description 'Displays information about available DISM command-line options and arguments (same as /?).'
        New-DismSwitchSpec -Token '/Online' -Description 'Targets the running operating system.'
        New-DismSwitchSpec -Token '/Image:' -Description 'Full path to the root directory of the offline Windows image to service.' -Kind 'Directory'
        New-DismSwitchSpec -Token '/WinDir:' -Description 'Path to the Windows directory relative to the image path (used with /Image).' -Kind 'Directory'
        New-DismSwitchSpec -Token '/SysDriveDir:' -Description 'Path to the location of the BootMgr files (Windows PE servicing).' -Kind 'Directory'
        New-DismSwitchSpec -Token '/LogPath:' -Description 'Full path and file name to log to (default %WINDIR%\Logs\Dism\dism.log).' -Kind 'File' -Extensions @('.log')
        New-DismSwitchSpec -Token '/LogLevel:' -Description 'Maximum output level shown in the logs: 1 errors, 2 warnings, 3 informational, 4 debug.' -Kind 'Enum' -Values @('1', '2', '3', '4')
        New-DismSwitchSpec -Token '/ScratchDir:' -Description 'Local temporary directory used when extracting files during servicing.' -Kind 'Directory'
        New-DismSwitchSpec -Token '/Quiet' -Description 'Turns off information and progress output to the console.'
        New-DismSwitchSpec -Token '/NoRestart' -Description 'Suppresses reboot and the restart prompt.'
        New-DismSwitchSpec -Token '/English' -Description 'Displays command-line output in English.'
        $s.Format
    )

    $commands = @(
        # Image management
        New-DismCommandSpec -Token '/Get-ImageInfo' -Description 'Displays information about the images in a .wim, .ffu, .vhd or .vhdx file.' -Switches @($s.ImageFile, $s.Index, $s.Name)
        New-DismCommandSpec -Token '/Get-MountedImageInfo' -Description 'Lists the images that are currently mounted.'
        New-DismCommandSpec -Token '/Mount-Image' -Description 'Mounts an image to a directory so it is available for servicing.' -Switches @($s.ImageFile, $s.Index, $s.Name, $s.MountDir,
            (New-DismSwitchSpec -Token '/ReadOnly' -Description 'Mounts the image with read-only permissions.'),
            (New-DismSwitchSpec -Token '/Optimize' -Description 'Reduces initial mount time.'),
            $s.CheckIntegrity)
        New-DismCommandSpec -Token '/Unmount-Image' -Description 'Unmounts an image and commits or discards the changes.' -Switches @($s.MountDir,
            (New-DismSwitchSpec -Token '/Commit' -Description 'Saves the changes made to the mounted image.'),
            (New-DismSwitchSpec -Token '/Discard' -Description 'Discards the changes made to the mounted image.'),
            $s.CheckIntegrity,
            (New-DismSwitchSpec -Token '/Append' -Description 'Adds the modified image to the .wim file instead of overwriting the original image.'))
        New-DismCommandSpec -Token '/Remount-Image' -Description 'Remounts a mounted image that has become inaccessible.' -Switches @($s.MountDir)
        New-DismCommandSpec -Token '/Commit-Image' -Description 'Applies the changes made to the mounted image without unmounting it.' -Switches @($s.MountDir, $s.CheckIntegrity,
            (New-DismSwitchSpec -Token '/Append' -Description 'Adds the modified image to the .wim file instead of overwriting the original image.'))
        New-DismCommandSpec -Token '/Cleanup-Mountpoints' -Description 'Deletes the resources associated with corrupted mounted images.'
        New-DismCommandSpec -Token '/Apply-Image' -Description 'Applies a .wim or split .swm image to a specified directory.' -Switches @($s.ImageFile, $s.SWMFile, $s.ApplyDir, $s.Index, $s.Name, $s.CheckIntegrity, $s.Verify, $s.NoRpFix,
            (New-DismSwitchSpec -Token '/ConfirmTrustedFile' -Description 'Validates the image for Trusted Desktop (WinPE 4.0 or later).'),
            $s.WIMBoot,
            (New-DismSwitchSpec -Token '/Compact' -Description 'Applies the image in compact mode, saving drive space.'),
            $s.EA)
        New-DismCommandSpec -Token '/Capture-Image' -Description 'Captures an image of a drive to a new .wim file.' -Switches @($s.ImageFile, $s.CaptureDir, $s.Name, $s.Description, $s.ConfigFile, $s.Compress, $s.Bootable, $s.WIMBoot, $s.CheckIntegrity, $s.Verify, $s.NoRpFix, $s.EA)
        New-DismCommandSpec -Token '/Capture-CustomImage' -Description 'Captures incremental file changes to a custom.wim for a WIMBoot image.' -Switches @($s.CaptureDir, $s.ConfigFile, $s.CheckIntegrity, $s.Verify,
            (New-DismSwitchSpec -Token '/ConfirmTrustedFile' -Description 'Validates the image for Trusted Desktop (WinPE 4.0 or later).'))
        New-DismCommandSpec -Token '/Append-Image' -Description 'Adds an additional image to a .wim file.' -Switches @($s.ImageFile, $s.CaptureDir, $s.Name, $s.Description, $s.ConfigFile, $s.Bootable, $s.WIMBoot, $s.CheckIntegrity, $s.Verify, $s.NoRpFix)
        New-DismCommandSpec -Token '/Export-Image' -Description 'Exports a copy of the specified image to another file.' -Switches @($s.SourceImageFile, $s.SourceIndex, $s.SourceName, $s.DestinationImageFile, $s.DestinationName, $s.SWMFile, $s.Compress, $s.Bootable, $s.WIMBoot, $s.CheckIntegrity)
        New-DismCommandSpec -Token '/Delete-Image' -Description 'Deletes a volume image from a .wim file with multiple images.' -Switches @($s.ImageFile, $s.Index, $s.Name, $s.CheckIntegrity)
        New-DismCommandSpec -Token '/Split-Image' -Description 'Splits an existing .wim file into multiple read-only .swm files.' -Switches @($s.ImageFile, $s.SWMFile, $s.FileSize, $s.CheckIntegrity)
        New-DismCommandSpec -Token '/List-Image' -Description 'Lists the files and folders in a specified image.' -Switches @($s.ImageFile, $s.Index, $s.Name)
        New-DismCommandSpec -Token '/Optimize-Image' -Description 'Optimizes an offline image (last command before applying it to a device).' -Switches @(
            (New-DismSwitchSpec -Token '/Boot' -Description 'Reduces the online configuration time the OS spends during boot.'),
            $s.WIMBoot)
        New-DismCommandSpec -Token '/Apply-FFU' -Description 'Applies a full flash update (.ffu) or split FFU image to a physical drive.' -Switches @($s.ImageFile,
            (New-DismSwitchSpec -Token '/ApplyDrive:' -Description 'Physical drive to image (for example \\.\PhysicalDrive0).' -Kind 'Value' -Placeholder '\\.\PhysicalDrive<n>'),
            $s.SFUFile)
        New-DismCommandSpec -Token '/Capture-FFU' -Description 'Captures an image of a physical drive to a new .ffu file.' -Switches @($s.ImageFile,
            (New-DismSwitchSpec -Token '/CaptureDrive:' -Description 'Physical drive to capture (for example \\.\PhysicalDrive0).' -Kind 'Value' -Placeholder '\\.\PhysicalDrive<n>'),
            $s.Name, $s.Description,
            (New-DismSwitchSpec -Token '/PlatformIds:' -Description 'Semicolon-separated platform ids to add to the image.' -Kind 'Value' -Placeholder '<platform-ids>'),
            (New-DismSwitchSpec -Token '/Compress:' -Description 'Compression used when capturing (use none when the FFU will be split).' -Kind 'Enum' -Values @('default', 'none')))
        New-DismCommandSpec -Token '/Split-FFU' -Description 'Splits an existing .ffu file into multiple read-only .sfu files.' -Switches @($s.ImageFile, $s.SFUFile, $s.FileSize, $s.CheckIntegrity)
        New-DismCommandSpec -Token '/Optimize-FFU' -Description 'Optimizes an FFU image so it deploys faster and to differently-sized disks.' -Switches @($s.ImageFile,
            (New-DismSwitchSpec -Token '/PartitionNumber:' -Description 'Partition to optimize (defaults to the OS partition).' -Kind 'Value' -Placeholder '<partition-number>'))
        New-DismCommandSpec -Token '/Apply-CustomDataImage' -Description 'Dehydrates files contained in a custom data image.' -Switches @(
            (New-DismSwitchSpec -Token '/CustomDataImage:' -Description 'Path to the custom data image (.wim).' -Kind 'File' -Extensions @('.wim')),
            (New-DismSwitchSpec -Token '/ImagePath:' -Description 'Path to the root of the applied Windows image.' -Kind 'Directory'),
            (New-DismSwitchSpec -Token '/SingleInstance' -Description 'Single-instances the files in the custom data image.'))
        New-DismCommandSpec -Token '/Apply-SiloedPackage' -Description 'Applies one or more siloed provisioning packages (.spp) to an applied image.' -Switches @(
            (New-DismSwitchSpec -Token '/PackagePath:' -Description 'Path of a siloed provisioning package file.' -Kind 'File' -Extensions @('.spp')),
            (New-DismSwitchSpec -Token '/ImagePath:' -Description 'Path of the Windows image where the SPP is applied.' -Kind 'Directory'))
        New-DismCommandSpec -Token '/Get-WIMBootEntry' -Description 'Displays WIMBoot configuration entries for the specified volume.' -Switches @(
            (New-DismSwitchSpec -Token '/Path:' -Description 'Disk volume of the WIMBoot configuration.' -Kind 'Directory'))
        New-DismCommandSpec -Token '/Update-WIMBootEntry' -Description 'Updates a WIMBoot configuration entry with a renamed or moved image file.' -Switches @(
            (New-DismSwitchSpec -Token '/Path:' -Description 'Disk volume of the WIMBoot configuration.' -Kind 'Directory'),
            (New-DismSwitchSpec -Token '/DataSourceID:' -Description 'Data source id as displayed by /Get-WIMBootEntry.' -Kind 'Value' -Placeholder '<data-source-id>'),
            $s.ImageFile)
        # OS package servicing
        New-DismCommandSpec -Token '/Get-Packages' -Description 'Displays basic information about all packages in the image.' -Switches @($s.Format)
        New-DismCommandSpec -Token '/Get-PackageInfo' -Description 'Displays detailed information about a package (.cab).' -Switches @($s.PackageName, $s.PackagePath)
        New-DismCommandSpec -Token '/Add-Package' -Description 'Installs a .cab or .msu package in the image.' -Switches @($s.PackagePath,
            (New-DismSwitchSpec -Token '/IgnoreCheck' -Description 'Skips the applicability check of each package.'),
            (New-DismSwitchSpec -Token '/PreventPending' -Description 'Skips the installation when the image has pending online actions.'))
        New-DismCommandSpec -Token '/Remove-Package' -Description 'Removes a .cab package from the image.' -Switches @($s.PackageName, $s.PackagePath)
        New-DismCommandSpec -Token '/Get-Features' -Description 'Displays basic information about all features in a package or the image.' -Switches @($s.PackageName, $s.PackagePath, $s.Format)
        New-DismCommandSpec -Token '/Get-FeatureInfo' -Description 'Displays detailed information about a feature.' -Switches @($s.FeatureName, $s.PackageName, $s.PackagePath)
        New-DismCommandSpec -Token '/Enable-Feature' -Description 'Enables or updates the specified feature in the image.' -Switches @($s.FeatureName, $s.PackageName, $s.PackagePath, $s.Source, $s.LimitAccess,
            (New-DismSwitchSpec -Token '/All' -Description 'Enables all parent features of the specified feature.'))
        New-DismCommandSpec -Token '/Disable-Feature' -Description 'Disables the specified feature in the image.' -Switches @($s.FeatureName, $s.PackageName, $s.PackagePath,
            (New-DismSwitchSpec -Token '/Remove' -Description 'Removes the feature payload without removing its manifest.'))
        New-DismCommandSpec -Token '/Cleanup-Image' -Description 'Performs cleanup or recovery operations on the image.' -Switches @(
            (New-DismSwitchSpec -Token '/CheckHealth' -Description 'Checks whether the image has been flagged as corrupted and whether it can be repaired.'),
            (New-DismSwitchSpec -Token '/ScanHealth' -Description 'Scans the image for component store corruption.'),
            (New-DismSwitchSpec -Token '/RestoreHealth' -Description 'Scans the image for corruption and performs repair operations automatically.'),
            $s.Source, $s.LimitAccess,
            (New-DismSwitchSpec -Token '/StartComponentCleanup' -Description 'Cleans up superseded components and reduces the size of the component store.'),
            (New-DismSwitchSpec -Token '/ResetBase' -Description 'Resets the base of superseded components (with /StartComponentCleanup).'),
            (New-DismSwitchSpec -Token '/Defer' -Description 'Defers long-running cleanup to the next automatic maintenance (with /ResetBase).'),
            (New-DismSwitchSpec -Token '/SPSuperseded' -Description 'Removes backup files created during a service pack installation.'),
            (New-DismSwitchSpec -Token '/HideSP' -Description 'Prevents the service pack from being listed in Installed Updates (with /SPSuperseded).'),
            (New-DismSwitchSpec -Token '/AnalyzeComponentStore' -Description 'Creates a report of the component store.'),
            (New-DismSwitchSpec -Token '/RevertPendingActions' -Description 'Reverts all pending actions from previous servicing operations (offline recovery only).'))
        # Capabilities
        New-DismCommandSpec -Token '/Get-Capabilities' -Description 'Displays the capabilities in the image.' -Switches @($s.Format, $s.Source, $s.LimitAccess)
        New-DismCommandSpec -Token '/Get-CapabilityInfo' -Description 'Displays detailed information about a capability.' -Switches @($s.CapabilityName, $s.Source, $s.LimitAccess)
        New-DismCommandSpec -Token '/Add-Capability' -Description 'Adds a capability to the image.' -Switches @($s.CapabilityName, $s.Source, $s.LimitAccess)
        New-DismCommandSpec -Token '/Remove-Capability' -Description 'Removes a capability from the image.' -Switches @($s.CapabilityName)
        New-DismCommandSpec -Token '/Export-Source' -Description 'Exports the source files of the capabilities in the image.' -Switches @($s.Source,
            (New-DismSwitchSpec -Token '/Target:' -Description 'Destination folder for the exported source.' -Kind 'Directory'),
            $s.CapabilityName)
        # Drivers
        New-DismCommandSpec -Token '/Get-Drivers' -Description 'Displays basic information about driver packages in the image.' -Switches @(
            (New-DismSwitchSpec -Token '/All' -Description 'Includes the default (inbox) drivers.'),
            $s.Format)
        New-DismCommandSpec -Token '/Get-DriverInfo' -Description 'Displays detailed information about a driver .inf file.' -Switches @($s.Driver)
        New-DismCommandSpec -Token '/Add-Driver' -Description 'Adds third-party driver packages to an offline image.' -Switches @($s.Driver,
            (New-DismSwitchSpec -Token '/Recurse' -Description 'Installs all drivers found in the folder and its subfolders.'),
            (New-DismSwitchSpec -Token '/ForceUnsigned' -Description 'Adds unsigned drivers to x64-based images.'))
        New-DismCommandSpec -Token '/Remove-Driver' -Description 'Removes third-party drivers from an offline image.' -Switches @($s.Driver)
        New-DismCommandSpec -Token '/Export-Driver' -Description 'Exports all third-party driver packages to a destination path.' -Switches @(
            (New-DismSwitchSpec -Token '/Destination:' -Description 'Folder the driver packages are exported to.' -Kind 'Directory'))
        # Editions
        New-DismCommandSpec -Token '/Get-CurrentEdition' -Description 'Displays the edition of the image.'
        New-DismCommandSpec -Token '/Get-TargetEditions' -Description 'Displays the editions the image can be upgraded to.'
        New-DismCommandSpec -Token '/Set-Edition:' -Description 'Changes the image to a higher edition (/Set-Edition:<target edition>).' -Kind 'Value' -Placeholder '<target-edition>' -Switches @(
            (New-DismSwitchSpec -Token '/ProductKey:' -Description 'Product key for the target edition.' -Kind 'Value' -Placeholder '<product-key>'),
            (New-DismSwitchSpec -Token '/AcceptEula' -Description 'Accepts the licence terms for the target edition.'),
            (New-DismSwitchSpec -Token '/Channel:' -Description 'Path to the licensing channel (.xrm-ms) file.' -Kind 'File' -Extensions @('.xrm-ms')))
        New-DismCommandSpec -Token '/Set-ProductKey:' -Description 'Enters the product key for the current edition (/Set-ProductKey:<key>).' -Kind 'Value' -Placeholder '<product-key>'
        # Unattend and app packages
        New-DismCommandSpec -Token '/Apply-Unattend:' -Description 'Applies an unattend.xml answer file to the image (/Apply-Unattend:<path>).' -Kind 'File' -Placeholder '<unattend.xml>'
        New-DismCommandSpec -Token '/Get-ProvisionedAppxPackages' -Description 'Displays the app packages (.appx/.appxbundle) provisioned in the image.'
        New-DismCommandSpec -Token '/Add-ProvisionedAppxPackage' -Description 'Adds one or more app packages to the image.' -Switches @(
            (New-DismSwitchSpec -Token '/FolderPath:' -Description 'Folder of unpacked app files (main package, dependencies, licence).' -Kind 'Directory'),
            (New-DismSwitchSpec -Token '/PackagePath:' -Description 'App package (.appx, .appxbundle, .msix, .msixbundle).' -Kind 'File' -Extensions @('.appx', '.appxbundle', '.msix', '.msixbundle')),
            (New-DismSwitchSpec -Token '/DependencyPackagePath:' -Description 'Dependency package needed by the app (repeatable).' -Kind 'File' -Extensions @('.appx', '.appxbundle', '.msix', '.msixbundle')),
            (New-DismSwitchSpec -Token '/LicensePath:' -Description 'Location of the .xml file containing the application licence.' -Kind 'File' -Extensions @('.xml')),
            (New-DismSwitchSpec -Token '/SkipLicense' -Description 'Skips the licence for apps that do not require one (sideloading-enabled computers).'),
            $s.CustomDataPath,
            (New-DismSwitchSpec -Token '/Region:' -Description 'Regions the package is provisioned for: all, or a semicolon-separated list of ISO 3166-1 codes.' -Kind 'Enum' -Values @('all')),
            (New-DismSwitchSpec -Token '/StubPackageOption:' -Description 'Stub preference of the package.' -Kind 'Enum' -Values @('installstub', 'installfull')))
        New-DismCommandSpec -Token '/Remove-ProvisionedAppxPackage' -Description 'Removes provisioning for an app package from the image.' -Switches @($s.PackageName)
        New-DismCommandSpec -Token '/Optimize-ProvisionedAppxPackages' -Description 'Optimizes the total file size of provisioned packages using hardlinks (offline only).'
        New-DismCommandSpec -Token '/Set-ProvisionedAppxDataFile' -Description 'Adds a custom data file into a provisioned app package.' -Switches @($s.CustomDataPath, $s.PackageName)
        New-DismCommandSpec -Token '/Get-DefaultAppAssociations' -Description 'Displays the default application associations in the image.'
        New-DismCommandSpec -Token '/Export-DefaultAppAssociations:' -Description 'Exports the default application associations to an XML file.' -Kind 'File' -Placeholder '<path.xml>'
        New-DismCommandSpec -Token '/Import-DefaultAppAssociations:' -Description 'Imports default application associations from an XML file.' -Kind 'File' -Placeholder '<path.xml>'
        New-DismCommandSpec -Token '/Remove-DefaultAppAssociations' -Description 'Removes the default application associations from the image.'
        # International servicing
        New-DismCommandSpec -Token '/Get-Intl' -Description 'Displays information about the international settings and languages.' -Switches @($s.Distribution)
        New-DismCommandSpec -Token '/Set-UILang:' -Description 'Sets the default system UI language (/Set-UILang:<language>).' -Kind 'Value' -Placeholder '<language>'
        New-DismCommandSpec -Token '/Set-UILangFallback:' -Description 'Sets the fallback system UI language (/Set-UILangFallback:<language>).' -Kind 'Value' -Placeholder '<language>'
        New-DismCommandSpec -Token '/Set-SysLocale:' -Description 'Sets the language for non-Unicode programs (/Set-SysLocale:<locale>).' -Kind 'Value' -Placeholder '<locale>'
        New-DismCommandSpec -Token '/Set-UserLocale:' -Description 'Sets the per-user format settings (/Set-UserLocale:<locale>).' -Kind 'Value' -Placeholder '<locale>'
        New-DismCommandSpec -Token '/Set-InputLocale:' -Description 'Sets the input locale and keyboard layout (/Set-InputLocale:<locale>).' -Kind 'Value' -Placeholder '<locale>'
        New-DismCommandSpec -Token '/Set-AllIntl:' -Description 'Sets all international settings to one language (/Set-AllIntl:<language>).' -Kind 'Value' -Placeholder '<language>'
        New-DismCommandSpec -Token '/Set-TimeZone:' -Description 'Sets the default time zone (/Set-TimeZone:<time zone>).' -Kind 'Value' -Placeholder '<time-zone>'
        New-DismCommandSpec -Token '/Set-SKUIntlDefaults:' -Description 'Sets the SKU international defaults for a language (/Set-SKUIntlDefaults:<language>).' -Kind 'Value' -Placeholder '<language>'
        New-DismCommandSpec -Token '/Set-LayeredDriver:' -Description 'Sets the keyboard layered driver (/Set-LayeredDriver:{1..6}).' -Kind 'Enum' -Values @('1', '2', '3', '4', '5', '6')
        New-DismCommandSpec -Token '/Gen-LangIni' -Description 'Generates a new Lang.ini file for Windows Setup.' -Switches @($s.Distribution)
        New-DismCommandSpec -Token '/Set-SetupUILang:' -Description 'Sets the default language used by Windows Setup (/Set-SetupUILang:<language>).' -Kind 'Value' -Placeholder '<language>' -Switches @($s.Distribution)
        # Windows PE servicing
        New-DismCommandSpec -Token '/Get-PESettings' -Description 'Displays the Windows PE settings in the image.'
        New-DismCommandSpec -Token '/Get-ScratchSpace' -Description 'Displays the configured Windows PE scratch space.'
        New-DismCommandSpec -Token '/Get-TargetPath' -Description 'Displays the target path of the Windows PE image.'
        New-DismCommandSpec -Token '/Set-ScratchSpace:' -Description 'Sets the Windows PE scratch space in MB (/Set-ScratchSpace:<size>).' -Kind 'Enum' -Values @('32', '64', '128', '256', '512')
        New-DismCommandSpec -Token '/Set-TargetPath:' -Description 'Sets the location of the Windows PE image on the disk (/Set-TargetPath:<path>).' -Kind 'Value' -Placeholder '<target-path>'
        New-DismCommandSpec -Token '/Enable-Profiling' -Description 'Enables Windows PE file logging (profiling).'
        New-DismCommandSpec -Token '/Disable-Profiling' -Description 'Disables Windows PE file logging (profiling).'
        New-DismCommandSpec -Token '/Get-Profiling' -Description 'Displays whether Windows PE profiling is enabled.'
        # Reserved storage
        New-DismCommandSpec -Token '/Get-ReservedStorageState' -Description 'Displays whether reserved storage is enabled.'
        New-DismCommandSpec -Token '/Set-ReservedStorageState' -Description 'Enables or disables reserved storage.' -Switches @(
            (New-DismSwitchSpec -Token '/State:' -Description 'Reserved storage state.' -Kind 'Enum' -Values @('Enabled', 'Disabled')))
    )

    [pscustomobject]@{
        GlobalSwitches = @($globals)
        Commands       = @($commands)
    }
}

function Get-DismOptionKey {
    param([string]$Token)

    if ([string]::IsNullOrWhiteSpace($Token)) {
        return $null
    }

    $clean = $Token.Trim('"')
    if ($clean -match '^(/[A-Za-z?][A-Za-z0-9\-]*:?)') {
        return $matches[1].ToLowerInvariant()
    }

    $null
}

function Test-DismElevated {
    try {
        $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        ([System.Security.Principal.WindowsPrincipal]$identity).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    } catch {
        $false
    }
}

function Invoke-DismHelpText {
    param([string[]]$Arguments, [int]$TimeoutMilliseconds = 2000)

    $command = Get-Command -Name 'dism.exe', 'dism' -CommandType Application -ErrorAction Ignore | Select-Object -First 1
    if (-not $command) {
        return @()
    }

    try {
        $startInfo = [System.Diagnostics.ProcessStartInfo]::new()
        $startInfo.FileName = $command.Source
        $startInfo.UseShellExecute = $false
        $startInfo.RedirectStandardInput = $true
        $startInfo.RedirectStandardOutput = $true
        $startInfo.RedirectStandardError = $true
        $startInfo.CreateNoWindow = $true
        foreach ($argument in @($Arguments)) {
            [void]$startInfo.ArgumentList.Add($argument)
        }
        [void]$startInfo.ArgumentList.Add('/?')

        $process = [System.Diagnostics.Process]::new()
        $process.StartInfo = $startInfo
        [void]$process.Start()
        $process.StandardInput.Close()
        $outputTask = $process.StandardOutput.ReadToEndAsync()
        $errorTask = $process.StandardError.ReadToEndAsync()
        if (-not $process.WaitForExit($TimeoutMilliseconds)) {
            try { $process.Kill($true) } catch { Write-Debug -Message $_.Exception.Message }
            return @()
        }

        [void]$errorTask.Result
        $text = ($outputTask.Result -replace '\e\[[0-9;?]*[ -/]*[@-~]', '')
        if ([string]::IsNullOrWhiteSpace($text) -or $text -match 'Error:\s*740' -or $text -match 'Elevated permissions are required') {
            return @()
        }

        @($text -split '\r?\n')
    } catch {
        @()
    }
}

function Get-DismSwitchTokensFromLines {
    param([string[]]$Lines)

    $tokens = foreach ($line in @($Lines)) {
        foreach ($match in [regex]::Matches($line, '(?<!\w)(/[A-Za-z][A-Za-z0-9\-]*:?)(?=[\s\]\}\|,]|$|<)')) {
            $match.Groups[1].Value
        }
    }

    @($tokens | Sort-Object -Unique)
}

function Add-DismLiveSwitchSet {
    param([string]$Key, [string[]]$HelpLines)

    # Enrichment only: tokens the static catalog does not know are added as plain flags under the given key.
    $known = $script:DismCompletionCatalog.OptionSpecs
    $extras = New-Object System.Collections.Generic.List[object]
    foreach ($token in Get-DismSwitchTokensFromLines -Lines $HelpLines) {
        $tokenKey = Get-DismOptionKey -Token $token
        if ($null -eq $tokenKey -or $known.ContainsKey($tokenKey) -or $known.ContainsKey($tokenKey + ':')) {
            continue
        }

        [void]$extras.Add((New-DismSwitchSpec -Token $token -Description "DISM option $token (from live help)."))
    }

    $script:DismCompletionCatalog.ExtraSwitchesByKey[$Key] = @($extras.ToArray())
}

function Initialize-DismCompletionCatalog {
    if ($script:DismCompletionCatalog.Initialized) {
        return
    }

    $static = Get-DismStaticCatalog
    $script:DismCompletionCatalog.Commands = @($static.Commands)
    $script:DismCompletionCatalog.GlobalSwitches = @($static.GlobalSwitches)

    $specs = @{}
    foreach ($switch in @($static.GlobalSwitches)) {
        $specs[(Get-DismOptionKey -Token $switch.Token)] = $switch
    }
    foreach ($command in @($static.Commands)) {
        $specs[(Get-DismOptionKey -Token $command.Token)] = $command
        foreach ($switch in @($command.Switches)) {
            $key = Get-DismOptionKey -Token $switch.Token
            if (-not $specs.ContainsKey($key)) {
                $specs[$key] = $switch
            }
        }
    }
    $script:DismCompletionCatalog.OptionSpecs = $specs
    $script:DismCompletionCatalog.Initialized = $true

    # DISM only prints help from an elevated shell; on any other shell the static catalog is the whole surface.
    if (Test-DismElevated) {
        $topHelp = Invoke-DismHelpText
        if ($topHelp.Count -gt 0) {
            $section = ''
            $liveCommands = New-Object System.Collections.Generic.List[object]
            foreach ($line in $topHelp) {
                if ($line -match '^\s*([A-Z][A-Z\s]+):\s*$') {
                    $section = $matches[1].Trim()
                    continue
                }

                if ($section -like '*COMMANDS*' -and $line -match '^\s*(/[A-Za-z][A-Za-z0-9\-]*:?)\s*(?:-|<|\{)') {
                    $token = $matches[1]
                    $key = Get-DismOptionKey -Token $token
                    if (-not $specs.ContainsKey($key) -and -not $specs.ContainsKey($key + ':')) {
                        $spec = New-DismCommandSpec -Token $token -Description "DISM command $token (from live help)."
                        [void]$liveCommands.Add($spec)
                        $specs[$key] = $spec
                    }
                }
            }

            if ($liveCommands.Count -gt 0) {
                $script:DismCompletionCatalog.Commands = @($static.Commands) + @($liveCommands.ToArray())
            }

            Add-DismLiveSwitchSet -Key '__top__' -HelpLines $topHelp
        }
    }
}

function Update-DismCommandCatalog {
    param([object]$Command, [bool]$Online)

    $key = Get-DismOptionKey -Token $Command.Token
    if ($script:DismCompletionCatalog.LiveCommandKeys.ContainsKey($key)) {
        return
    }
    $script:DismCompletionCatalog.LiveCommandKeys[$key] = $true

    if (-not (Test-DismElevated)) {
        return
    }

    $arguments = @()
    if ($Online) { $arguments += '/Online' }
    $arguments += $Command.Token.TrimEnd(':')
    $help = Invoke-DismHelpText -Arguments $arguments
    if ($help.Count -gt 0) {
        Add-DismLiveSwitchSet -Key $key -HelpLines $help
    }
}

function Get-DismActiveCommand {
    param([string[]]$Tokens)

    foreach ($token in @($Tokens)) {
        $key = Get-DismOptionKey -Token $token
        if ($null -eq $key) {
            continue
        }

        foreach ($command in @($script:DismCompletionCatalog.Commands)) {
            $commandKey = Get-DismOptionKey -Token $command.Token
            if ($commandKey -eq $key -or ($command.Token.EndsWith(':') -and $key -eq $commandKey.TrimEnd(':'))) {
                return $command
            }
        }
    }

    $null
}

function Get-DismOptionSpec {
    param([string]$Key, [object]$ActiveCommand)

    if ($ActiveCommand) {
        foreach ($switch in @($ActiveCommand.Switches)) {
            if ((Get-DismOptionKey -Token $switch.Token) -eq $Key) {
                return $switch
            }
        }
    }

    if ($script:DismCompletionCatalog.OptionSpecs.ContainsKey($Key)) {
        return $script:DismCompletionCatalog.OptionSpecs[$Key]
    }

    $null
}

function New-DismCompletionResult {
    param([string]$CompletionText, [string]$ListItemText, [string]$ResultType, [string]$ToolTip)

    if ([string]::IsNullOrWhiteSpace($ListItemText)) { $ListItemText = $CompletionText }
    if ([string]::IsNullOrWhiteSpace($ToolTip)) { $ToolTip = $CompletionText }
    [System.Management.Automation.CompletionResult]::new($CompletionText, $ListItemText, $ResultType, $ToolTip)
}

function Get-DismPathCompletions {
    param(
        [string]$InputPath,
        [string]$Kind,
        [string[]]$AllowedExtensions
    )

    # Split on the last separator instead of Split-Path: '', 'C:\' and '.\' must list that directory's children
    # and the typed prefix must be preserved verbatim.
    $cleanInput = if ($null -eq $InputPath) { '' } else { $InputPath }
    $separatorIndex = $cleanInput.LastIndexOfAny([char[]]@('\', '/'))
    if ($separatorIndex -ge 0) {
        $parentText = $cleanInput.Substring(0, $separatorIndex + 1)
        $leaf = $cleanInput.Substring($separatorIndex + 1)
    } else {
        $parentText = ''
        $leaf = $cleanInput
    }

    $parent = if ([string]::IsNullOrEmpty($parentText)) { '.' } else { $parentText }
    $pattern = [System.Management.Automation.WildcardPattern]::Escape($leaf) + '*'
    $extensions = @($AllowedExtensions | ForEach-Object { $_.ToLowerInvariant() })

    $items = @(Get-ChildItem -LiteralPath $parent -ErrorAction SilentlyContinue | Where-Object { $_.Name -like $pattern } | Sort-Object -Property Name)
    foreach ($item in $items) {
        if (-not $item.PSIsContainer) {
            if ($Kind -eq 'Directory') { continue }
            if ($extensions.Count -gt 0 -and ($extensions -notcontains $item.Extension.ToLowerInvariant())) { continue }
        }

        $text = $parentText + $item.Name
        if ($item.PSIsContainer) { $text += [System.IO.Path]::DirectorySeparatorChar }
        [pscustomobject]@{
            Text        = $text
            IsContainer = $item.PSIsContainer
            FullName    = $item.FullName
        }
    }
}

function Get-DismOptionValueCompletion {
    param(
        [string]$OptionPrefix,
        [string]$TypedValue,
        [object]$Spec,
        [string]$QuoteStyle
    )

    $valueQuoted = $TypedValue.StartsWith('"')
    $cleanValue = $TypedValue.Trim('"')
    if ($valueQuoted -and $QuoteStyle -eq 'None') { $QuoteStyle = 'Value' }

    switch ($Spec.Kind) {
        'Enum' {
            foreach ($value in @($Spec.Values)) {
                if ($value.StartsWith($cleanValue, [System.StringComparison]::OrdinalIgnoreCase)) {
                    New-DismCompletionResult -CompletionText ($OptionPrefix + $value) -ResultType 'ParameterValue' -ToolTip $Spec.Description
                }
            }
            return
        }
        { $_ -in @('Directory', 'File') } {
            $paths = @(Get-DismPathCompletions -InputPath $cleanValue -Kind $Spec.Kind -AllowedExtensions $Spec.Extensions)
            if ($paths.Count -eq 0) {
                if ([string]::IsNullOrEmpty($cleanValue)) {
                    $hint = if ($Spec.Kind -eq 'Directory') { '<directory>' } else { '<path>' }
                    New-DismCompletionResult -CompletionText ($OptionPrefix + $hint) -ResultType 'ParameterValue' -ToolTip $Spec.Description
                }
                return
            }

            foreach ($path in $paths) {
                $style = $QuoteStyle
                if ($style -eq 'None' -and $path.Text -match '\s') { $style = 'Token' }
                $text = switch ($style) {
                    'Token' { '"' + $OptionPrefix + $path.Text + '"' }
                    'Value' { $OptionPrefix + '"' + $path.Text + '"' }
                    default { $OptionPrefix + $path.Text }
                }
                $resultType = if ($path.IsContainer) { 'ProviderContainer' } else { 'ProviderItem' }
                New-DismCompletionResult -CompletionText $text -ListItemText ($OptionPrefix + $path.Text) -ResultType $resultType -ToolTip $path.FullName
            }
            return
        }
        default {
            if ([string]::IsNullOrEmpty($cleanValue)) {
                New-DismCompletionResult -CompletionText ($OptionPrefix + $Spec.Placeholder) -ResultType 'ParameterValue' -ToolTip $Spec.Description
            }
        }
    }
}

function Get-DismSwitchSuggestion {
    param([object]$ActiveCommand, [string]$CurrentWord)

    $suggestions = New-Object System.Collections.Generic.List[object]
    $seen = @{}
    $candidates = New-Object System.Collections.Generic.List[object]

    if ($ActiveCommand) {
        foreach ($switch in @($ActiveCommand.Switches)) { [void]$candidates.Add($switch) }
        $commandKey = Get-DismOptionKey -Token $ActiveCommand.Token
        if ($script:DismCompletionCatalog.ExtraSwitchesByKey.ContainsKey($commandKey)) {
            foreach ($switch in @($script:DismCompletionCatalog.ExtraSwitchesByKey[$commandKey])) { [void]$candidates.Add($switch) }
        }
        foreach ($switch in @($script:DismCompletionCatalog.GlobalSwitches)) { [void]$candidates.Add($switch) }
    } else {
        foreach ($command in @($script:DismCompletionCatalog.Commands)) { [void]$candidates.Add($command) }
        foreach ($switch in @($script:DismCompletionCatalog.GlobalSwitches)) { [void]$candidates.Add($switch) }
        if ($script:DismCompletionCatalog.ExtraSwitchesByKey.ContainsKey('__top__')) {
            foreach ($switch in @($script:DismCompletionCatalog.ExtraSwitchesByKey['__top__'])) { [void]$candidates.Add($switch) }
        }
    }

    foreach ($candidate in $candidates) {
        $key = $candidate.Token.ToLowerInvariant()
        if ($seen.ContainsKey($key)) { continue }
        if (-not [string]::IsNullOrEmpty($CurrentWord) -and -not $candidate.Token.StartsWith($CurrentWord, [System.StringComparison]::OrdinalIgnoreCase)) { continue }
        $seen[$key] = $true
        [void]$suggestions.Add((New-DismCompletionResult -CompletionText $candidate.Token -ResultType 'ParameterName' -ToolTip $candidate.Description))
    }

    @($suggestions.ToArray())
}

function Complete-Dism {
    param(
        [string]$WordToComplete,
        [System.Management.Automation.Language.CommandAst]$CommandAst,
        [int]$CursorPosition
    )

    Initialize-DismCompletionCatalog

    # Tokens that end before the cursor form the context; the element under the cursor is the current word, cut at
    # the cursor; anything to the right is ignored.
    $tokensBefore = New-Object System.Collections.Generic.List[string]
    $currentWord = ''
    foreach ($element in @($CommandAst.CommandElements | Select-Object -Skip 1)) {
        $extent = $element.Extent
        if ($extent.EndOffset -lt $CursorPosition) {
            [void]$tokensBefore.Add($extent.Text)
            continue
        }
        if ($extent.StartOffset -le $CursorPosition) {
            $currentWord = $extent.Text.Substring(0, $CursorPosition - $extent.StartOffset)
        }
        break
    }
    if ([string]::IsNullOrEmpty($currentWord) -and -not [string]::IsNullOrEmpty($WordToComplete)) { $currentWord = $WordToComplete }

    $quoteStyle = 'None'
    $cleanWord = $currentWord
    if ($cleanWord.StartsWith('"')) {
        $quoteStyle = 'Token'
        $cleanWord = $cleanWord.Trim('"')
    }

    $activeCommand = Get-DismActiveCommand -Tokens @($tokensBefore.ToArray())
    $online = [bool](@($tokensBefore.ToArray()) | Where-Object { (Get-DismOptionKey -Token $_) -eq '/online' })
    if ($activeCommand) {
        Update-DismCommandCatalog -Command $activeCommand -Online $online
    }

    if ($cleanWord -match '^(/[A-Za-z][A-Za-z0-9\-]*:)(.*)$') {
        $optionPrefix = $matches[1]
        $typedValue = $matches[2]
        $spec = Get-DismOptionSpec -Key $optionPrefix.ToLowerInvariant() -ActiveCommand $activeCommand
        if ($spec) {
            return @(Get-DismOptionValueCompletion -OptionPrefix $optionPrefix -TypedValue $typedValue -Spec $spec -QuoteStyle $quoteStyle)
        }

        return @()
    }

    if ($cleanWord.StartsWith('/') -or [string]::IsNullOrWhiteSpace($cleanWord)) {
        return @(Get-DismSwitchSuggestion -ActiveCommand $activeCommand -CurrentWord $cleanWord)
    }

    if ($cleanWord -like '*\*' -or $cleanWord -like '[A-Za-z]:*') {
        return @(Get-DismPathCompletions -InputPath $cleanWord -Kind 'File' -AllowedExtensions @() | ForEach-Object {
            New-DismCompletionResult -CompletionText $_.Text -ResultType $(if ($_.IsContainer) { 'ProviderContainer' } else { 'ProviderItem' }) -ToolTip $_.FullName
        })
    }

    @()
}

Register-ArgumentCompleter -Native -CommandName 'dism' -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)

    Complete-Dism -WordToComplete $wordToComplete -CommandAst $commandAst -CursorPosition $cursorPosition
}