DpkgPackage.psm1

#Region './prefix.ps1' 0
Import-Module -Name $PSScriptRoot\Modules\PSNativeCmdDevKit -ErrorAction Stop
#EndRegion './prefix.ps1' 1
#Region './Classes/1.Package.ps1' 0
if (-not ('Package' -as [type]))
{
    class Package
    {
        $Name
        $PackageType
        $Version
        $Vendor
    }

}
#EndRegion './Classes/1.Package.ps1' 11
#Region './Classes/DpkgPackage.ps1' 0
#using module Package
class DpkgPackage : Package
{
    # https://www.debian.org/doc/debian-policy/ch-controlfields.html

    # This field identifies the source package name.
    $Source

    # The package maintainer’s name and email address.
    # The name must come first, then the email address inside angle brackets <> (in RFC822 format).
    $Maintainer

    # List of the names and email addresses of co-maintainers of the package, if any.
    $Uploaders

    # The name and email address of the person who prepared this version of the package,
    # usually a maintainer. The syntax is the same as for the Maintainer field.
    $ChangedBy

    # This field specifies an application area into which the package has been classified.
    # See Sections.
    $Section

    # This field represents how important it is that the user have the package installed.
    # See Priorities.
    $Priority

    # The name of the binary package.
    # Binary package names must follow the same syntax and restrictions as source package names.
    # See Source for the details.
    # This also populates the Name property of the [Package] parent class
    $Package

    # Depending on context and the control file used, the Architecture field can include the following sets of values:
    # - A unique single word identifying a Debian machine architecture as described in Architecture specification strings.
    # (https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s-arch-spec)
    # - An architecture wildcard identifying a set of Debian machine architectures, see Architecture wildcards.
    # (https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s-arch-wildcard-spec)
    # `any` matches all Debian machine architectures and is the most frequently used.
    # - all, which indicates an architecture-independent package.
    # - source, which indicates a source package.
    $Architecture

    # This is a boolean field which may occur only in the control file of a binary package or
    # in a per-package fields paragraph of a source package control file.
    # If set to yes then the package management system will refuse to remove the package
    # (upgrading and replacing it is still possible).
    # The other possible value is no, which is the same as not having the field at all.
    $Essential

    #region Package interrelationship fields
    # These fields describe the package’s relationships with other packages.
    # Their syntax and semantics are described in Declaring relationships between packages.
    # (https://www.debian.org/doc/debian-policy/ch-relationships.html)

    $Depends
    $PreDepends
    $Recommends
    $Suggests
    $Breaks
    $Conflicts
    $Provides
    $Replaces
    $Enhances

    #endregion Package interrelationship fields

    $StandardsVersion

    # $Version # defined in Parent Class [Package]
    $Description

    $Distribution
    $Date
    $Format
    $Urgency
    $Changes
    $Binary
    $InstalledSize
    $Files
    $Closes
    $Homepage
    $ChecksumsSha1
    $ChecksumsSha256
    $DMUploadAllowed # obsolete
    $PackageList
    $PackageType
    $Dgit
    $TestSuite
    $RulesRequiresRoot

    # Additional Fields
    $Status
    $OriginalMaintainer
    $MultiArch
    $Conffiles
    $AdditionalFields = @{}
}
#EndRegion './Classes/DpkgPackage.ps1' 98
#Region './Private/Get-DpkgCmdArgument.ps1' 0
# Usage: dpkg [<option> ...] <command>

# Commands:
# -i|--install <.deb file name> ... | -R|--recursive <directory> ...
# --unpack <.deb file name> ... | -R|--recursive <directory> ...
# -A|--record-avail <.deb file name> ... | -R|--recursive <directory> ...
# --configure <package> ... | -a|--pending
# --triggers-only <package> ... | -a|--pending
# -r|--remove <package> ... | -a|--pending
# -P|--purge <package> ... | -a|--pending
# -V|--verify <package> ... Verify the integrity of package(s).
# --get-selections [<pattern> ...] Get list of selections to stdout.
# --set-selections Set package selections from stdin.
# --clear-selections Deselect every non-essential package.
# --update-avail [<Packages-file>] Replace available packages info.
# --merge-avail [<Packages-file>] Merge with info from file.
# --clear-avail Erase existing available info.
# --forget-old-unavail Forget uninstalled unavailable pkgs.
# -s|--status <package> ... Display package status details.
# -p|--print-avail <package> ... Display available version details.
# -L|--listfiles <package> ... List files 'owned' by package(s).
# -l|--list [<pattern> ...] List packages concisely.
# -S|--search <pattern> ... Find package(s) owning file(s).
# -C|--audit [<package> ...] Check for broken package(s).
# --yet-to-unpack Print packages selected for installation.
# --predep-package Print pre-dependencies to unpack.
# --add-architecture <arch> Add <arch> to the list of architectures.
# --remove-architecture <arch> Remove <arch> from the list of architectures.
# --print-architecture Print dpkg architecture.
# --print-foreign-architectures Print allowed foreign architectures.
# --assert-<feature> Assert support for the specified feature.
# --validate-<thing> <string> Validate a <thing>'s <string>.
# --compare-versions <a> <op> <b> Compare version numbers - see below.
# --force-help Show help on forcing.
# -Dh|--debug=help Show help on debugging.

# -?, --help Show this help message.
# --version Show the version.

# Assertable features: support-predepends, working-epoch, long-filenames,
# multi-conrep, multi-arch, versioned-provides.

# Validatable things: pkgname, archname, trigname, version.

# Use dpkg with -b, --build, -c, --contents, -e, --control, -I, --info,
# -f, --field, -x, --extract, -X, --vextract, --ctrl-tarfile, --fsys-tarfile
# on archives (type dpkg-deb --help).

# Options:
# --admindir=<directory> Use <directory> instead of /var/lib/dpkg.
# --root=<directory> Install on a different root directory.
# --instdir=<directory> Change installation dir without changing admin dir.
# --path-exclude=<pattern> Do not install paths which match a shell pattern.
# --path-include=<pattern> Re-include a pattern after a previous exclusion.
# -O|--selected-only Skip packages not selected for install/upgrade.
# -E|--skip-same-version Skip packages whose same version is installed.
# -G|--refuse-downgrade Skip packages with earlier version than installed.
# -B|--auto-deconfigure Install even if it would break some other package.
# --[no-]triggers Skip or force consequential trigger processing.
# --verify-format=<format> Verify output format (supported: 'rpm').
# --no-debsig Do not try to verify package signatures.
# --no-act|--dry-run|--simulate
# Just say what we would do - don't do it.
# -D|--debug=<octal> Enable debugging (see -Dhelp or --debug=help).
# --status-fd <n> Send status change updates to file descriptor <n>.
# --status-logger=<command> Send status change updates to <command>'s stdin.
# --log=<filename> Log status changes and actions to <filename>.
# --ignore-depends=<package>,...
# Ignore dependencies involving <package>.
# --force-... Override problems (see --force-help).
# --no-force-...|--refuse-...
# Stop when problems encountered.

function Get-DpkgCmdArgument
{
    [CmdletBinding()]
    param
    (
        #Commands
        [Parameter()]
        $Info,

        [Parameter()]
        $Install,

        [Parameter()]
        $Unpack,

        [Parameter()]
        $RecordAvailble,

        [Parameter()]
        $Configure,

        [Parameter()]
        $TriggersOnly,

        [Parameter()]
        $Remove,

        [Parameter()]
        $Purge,

        [Parameter()]
        $Verify,

        [Parameter()]
        $GetSelections,

        [Parameter()]
        $SetSelections,

        [Parameter()]
        $UpdateAvailable,

        [Parameter()]
        $MergeAvailable,

        [Parameter()]
        $ClearAvailable,

        [Parameter()]
        $ForgetOldUnavailable,

        [Parameter()]
        [AllowNull()]
        [string]
        $Status,

        [Parameter()]
        $PrintAvailable,

        [Parameter()]
        $ListFiles,


        [Parameter()]
        [AllowNull()]
        [String]
        $List,

        [Parameter()]
        $Search,

        [Parameter()]
        $Audit,

        [Parameter()]
        $YetToUnpack,

        [Parameter()]
        $Predependencies,

        [Parameter()]
        $AddArchitecture,

        [Parameter()]
        $RemoveArchitecture,

        [Parameter()]
        $PrintArchitecture,

        [Parameter()]
        $PrintForeignArchitecture,

        [Parameter()]
        $Assert,

        [Parameter()]
        $Validate,

        [Parameter()]
        $CompareVersion,

        [Parameter()]
        $ForceHelp,

        [Parameter()]
        $DebugHelp,

        [Parameter()]
        $Help,


        # options

        [Parameter()]
        $AdminDir,

        [Parameter()]
        $RootDir,

        [Parameter()]
        $InstallDir,

        [Parameter()]
        $PathExclude,

        [Parameter()]
        $PathInclude,

        [Parameter()]
        $SelectedOnly,

        [Parameter()]
        $SkipSameVersion,

        [Parameter()]
        $RefuseDowngrade,

        [Parameter()]
        $AutoDeconfigure,

        [Parameter()]
        $NoTriggers,

        [Parameter()]
        $Triggers,

        [Parameter()]
        $VerifyFormat,

        [Parameter()]
        $NoDebsig,

        #WhatIf == --dry-run
        [Parameter()]
        $StatusFileDescriptor,

        [Parameter()]
        $StatusLogger,

        [Parameter()]
        $Log,

        [Parameter()]
        $IgnoreDepends,

        [Parameter()]
        $Force
        # Refuse / --no-force -eq ErrorAction Stop
    )

    $dpkgOption = switch ($PSBoundParameters.Keys)
    {
        'AdminDir'             { throw 'Not Implemented Yet.' }
        'RootDir'              { throw 'Not Implemented Yet.' }
        'InstallDir'           { throw 'Not Implemented Yet.' }
        'PathExclude'          { throw 'Not Implemented Yet.' }
        'PathInclude'          { throw 'Not Implemented Yet.' }
        'SelectedOnly'         { throw 'Not Implemented Yet.' }
        'SkipSameVersion'      { throw 'Not Implemented Yet.' }
        'RefuseDowngrade'      { throw 'Not Implemented Yet.' }
        'AutoDeconfigure'      { throw 'Not Implemented Yet.' }
        'NoTriggers'           { throw 'Not Implemented Yet.' }
        'Triggers'             { throw 'Not Implemented Yet.' }
        'VerifyFormat'         { throw 'Not Implemented Yet.' }
        'NoDebsig'             { throw 'Not Implemented Yet.' }
        # WhatIf == --dry-run { throw 'Not Implemented Yet.' }
        'StatusFileDescriptor' { throw 'Not Implemented Yet.' }
        'StatusLogger'         { throw 'Not Implemented Yet.' }
        'Log'                  { throw 'Not Implemented Yet.' }
        'IgnoreDepends'        { throw 'Not Implemented Yet.' }
        'Force'                { throw 'Not Implemented Yet.' }
        # Refuse / --no-force -eq ErrorAction Stop
    }

    $dpkgCommand = switch ($PSBoundParameters.Keys)
    {
        'Info'                     { "--info '$($Info -join "'")'" }
        'Install'                  { throw 'Not Implemented Yet.'}
        'Unpack'                   { throw 'Not Implemented Yet.'}
        'RecordAvailble'           { throw 'Not Implemented Yet.'}
        'Configure'                { throw 'Not Implemented Yet.'}
        'TriggersOnly'             { throw 'Not Implemented Yet.'}
        'Remove'                   { throw 'Not Implemented Yet.'}
        'Purge'                    { throw 'Not Implemented Yet.'}
        'Verify'                   { throw 'Not Implemented Yet.'}
        'GetSelections'            { throw 'Not Implemented Yet.'}
        'SetSelections'            { throw 'Not Implemented Yet.'}
        'UpdateAvailable'          { throw 'Not Implemented Yet.'}
        'MergeAvailable'           { throw 'Not Implemented Yet.'}
        'ClearAvailable'           { throw 'Not Implemented Yet.'}
        'ForgetOldUnavailable'     { throw 'Not Implemented Yet.'}
        'Status'                   { "-s$(if ($Status) {" '$Status'"})"}
        'PrintAvailable'           { throw 'Not Implemented Yet.'}
        'ListFiles'                { throw 'Not Implemented Yet.'}
        'List'                     { "-l $List" }
        'Search'                   { throw 'Not Implemented Yet.'}
        'Audit'                    { throw 'Not Implemented Yet.'}
        'YetToUnpack'              { throw 'Not Implemented Yet.'}
        'Predependencies'          { throw 'Not Implemented Yet.'}
        'AddArchitecture'          { throw 'Not Implemented Yet.'}
        'RemoveArchitecture'       { throw 'Not Implemented Yet.'}
        'PrintArchitecture'        { throw 'Not Implemented Yet.'}
        'PrintForeignArchitecture' { throw 'Not Implemented Yet.'}
        'Assert'                   { throw 'Not Implemented Yet.'}
        'Validate'                 { throw 'Not Implemented Yet.'}
        'CompareVersion'           { throw 'Not Implemented Yet.'}
        'ForceHelp'                { throw 'Not Implemented Yet.'}
        'DebugHelp'                { throw 'Not Implemented Yet.'}
        'Help'                     { throw 'Not Implemented Yet.'}
    }

    return ($dpkgOption + $dpkgCommand)
}
#EndRegion './Private/Get-DpkgCmdArgument.ps1' 306
#Region './Private/Get-DpkgQueryCmdArgument.ps1' 0
# Usage: dpkg-query [<option> ...] <command>

# Commands:
# -s|--status <package> ... Display package status details.
# -p|--print-avail <package> ... Display available version details.
# -L|--listfiles <package> ... List files 'owned' by package(s).
# -l|--list [<pattern> ...] List packages concisely.
# -W|--show [<pattern> ...] Show information on package(s).
# -S|--search <pattern> ... Find package(s) owning file(s).
# --control-list <package> Print the package control file list.
# --control-show <package> <file>
# Show the package control file.
# -c|--control-path <package> [<file>]
# Print path for package control file.

# -?, --help Show this help message.
# --version Show the version.

# Options:
# --admindir=<directory> Use <directory> instead of /var/lib/dpkg.
# --load-avail Use available file on --show and --list.
# -f|--showformat=<format> Use alternative format for --show.

# Format syntax:
# A format is a string that will be output for each package. The format
# can include the standard escape sequences \n (newline), \r (carriage
# return) or \\ (plain backslash). Package information can be included
# by inserting variable references to package fields using the ${var[;width]}
# syntax. Fields will be right-aligned unless the width is negative in which
# case left alignment will be used.

function Get-DpkgQueryCmdArgument
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        $Show,

        [Parameter()]
        $List,

        [Parameter()]
        $ListFiles
    )

    $dpkgOption = switch ($PSBoundParameters.Keys)
    {
        'AdminDir' { throw 'Not Implemented Yet.' }
    }

    $dpkgCommand = switch ($PSBoundParameters.Keys)
    {
# -s|--status <package> ... Display package status details.
# -p|--print-avail <package> ... Display available version details.

# -L|--listfiles <package> ... List files 'owned' by package(s).
        'ListFiles' {"-L '$ListFiles"}
# -l|--list [<pattern> ...] List packages concisely.
        'List' { "-l $List"}
# -W|--show [<pattern> ...] Show information on package(s).
        'Show' { "-W '$($Show -join "' '")'"}

# -S|--search <pattern> ... Find package(s) owning file(s).
# --control-list <package> Print the package control file list.
# --control-show <package> <file>
# Show the package control file.
# -c|--control-path <package> [<file>]
# Print path for package control file.

# -?, --help Show this help message.
# --version Show the version.
    }

    return ($dpkgOption + $dpkgCommand)
}
#EndRegion './Private/Get-DpkgQueryCmdArgument.ps1' 76
#Region './Public/Get-DpkgInstalledPackage.ps1' 0
function Get-DpkgInstalledPackage
{
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)]
        [Alias('Package')]
        [string[]]
        $Name
    )

    process {
        # Debian policy says Package name must be lowercase, making the user a service by forcing ToLower()
        # https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-source
        $Name = $Name.ForEach{$_.ToLower()}

        Invoke-NativeCommand -Executable 'dpkg-query' -Parameters (Get-DpkgQueryCmdArgument -Show $Name) |
          ForEach-Object {
            $dpkgPackage = $_ -split "`t"
            if ($_ -is [System.Management.Automation.ErrorRecord])
            {
                switch -Regex ($_)
                {
                    # this Adds a way to process the error stream in a customized way.
                    # 'no\spackages\sfound' { throw "Package $($Name) not found." } # Use this if you wan to throw when this error is raised
                    default { Write-Error "$_." }
                }
            }
            else {
                [PSCustomObject]@{
                    PSTypeName  = 'DpkgPackage.Installed'
                    Name        = $dpkgPackage[0]
                    Version     = $dpkgPackage[1]
                }
            }
          }
    }
}
#EndRegion './Public/Get-DpkgInstalledPackage.ps1' 38
#Region './Public/Get-DpkgPackage.ps1' 0
function Get-DpkgPackage
{
    [CmdletBinding(DefaultParameterSetName = 'dpkgInstalledPackage')]
    param
    (
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'dpkgInstalledPackage', Position = 0)]
        [Alias('Package')]
        [string[]]
        $Name,

        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'dpkgFile', Position = 0)]
        $Path
    )

    process
    {

        switch ($PSCmdlet.ParameterSetName)
        {
            'dpkgInstalledPackage' {
                $PackageToParse = { Get-DpkgInstalledPackage -Name $Name | Where-Object {$null -ne $_.Version} }
            }

            'dpkgFile'  {
                $PackageToParse = { Get-Item -Path $Path }
            }
        }

        &$PackageToParse | ForEach-Object {
            if ($_ -is [System.IO.FileInfo])
            {
                # dpkg --info ./localpackage.deb for getting info of non-installed package
                $dpkgParams = (Get-DpkgCmdArgument -Info $_.FullName)
            }
            else
            {
                # dpkg --status packageName for having details of the installed package
                $dpkgParams = (Get-DpkgCmdArgument -Status $_.Name)
            }

            Write-Verbose "Fetching details for '$($_.Name)'"
            $GetPropertyHashFromListOutputParams = @{
                AllowedPropertyName     = ([DpkgPackage].GetProperties().Name)
                AddExtraPropertiesAsKey = 'AdditionalFields'
                ErrorVariable           = 'packageError'
            }

            $properties = Invoke-NativeCommand -Executable 'dpkg' -Parameters $dpkgParams |
                Get-PropertyHashFromListOutput @GetPropertyHashFromListOutputParams

            # Making sure we replicate the package property to Name property
            # To correctly make the Base object (Package class)
            $properties['PackageType'] = 'dpkg'
            $properties.add('Name', $properties['Package'])

            if (-not $packageError)
            {
                [DpkgPackage]$properties
            }
        }
    }
}
#EndRegion './Public/Get-DpkgPackage.ps1' 62