adamcook-mycicdtestrepo.psm1

#region Private functions
function ConvertTo-ModelNameCIID {
    <#
    .SYNOPSIS
        Get a ConfigMgr Application's CI_ID property from the given ModelName property
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$ModelName,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer,
        
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode
    )
    begin {
        $Namespace = "ROOT/SMS/Site_{0}" -f $SiteCode
    }
    process {
        $Query = "SELECT CI_ID FROM SMS_ApplicationLatest WHERE ModelName = '{0}'" -f $ModelName
        (Get-CimInstance -ComputerName $SiteServer -Namespace $Namespace -Query $Query).CI_ID
    }
    end {

    }
}

function ConvertTo-PackageIDCIID {
    <#
    .SYNOPSIS
        Get a ConfigMgr Application's CI_ID property from the given PackageID property
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$PackageID,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer,
        
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode
    )
    begin {
        $Namespace = "ROOT/SMS/Site_{0}" -f $SiteCode
    }
    process {
        $Query = "SELECT SMS_ApplicationLatest.CI_ID
        FROM SMS_ApplicationLatest
        WHERE SMS_ApplicationLatest.ModelName in (
            SELECT SMS_PackageStatusDistPointsSummarizer.SecureObjectID
            FROM SMS_PackageStatusDistPointsSummarizer
            WHERE PackageID = '{0}'
        )"
 -f $PackageID
        (Get-CimInstance -ComputerName $SiteServer -Namespace $Namespace -Query $Query).CI_ID
    }
    end {

    }
}

function Find-CMApplication {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ParameterSetName="ModelName")]
        [String]$ModelName,
        [Parameter(Mandatory, ParameterSetName="CI_ID")]
        [String]$CI_ID,
        [Parameter(Mandatory)]
        [Hashtable]$CimParams
    )

    $Query = "SELECT CI_ID,LocalizedDisplayName,LocalizedDescription FROM SMS_ApplicationLatest WHERE {0} = '{1}'" -f $PSCmdlet.ParameterSetName, (Get-Variable -Name $PSCmdlet.ParameterSetName).Value
    Get-CimInstance -Query $Query @CimParams | Select-Object -Property @(
        @{Label="Name";Expression={$_.LocalizedDisplayName}}
        @{Label="Description";Expression={$_.LocalizedDescription}}
        @{Label="ObjectType";Expression={"Application"}}
        "CI_ID"
    )
}

function Find-CMCICB {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ParameterSetName="ModelName")]
        [String]$ModelName,
        [Parameter(Mandatory, ParameterSetName="CI_ID")]
        [String]$CI_ID,
        [Parameter(Mandatory)]
        [Hashtable]$CimParams
    )
    $Query = "SELECT CI_ID,LocalizedDisplayName,CIType_ID FROM SMS_ConfigurationItemLatest WHERE {0} = '{1}'" -f $PSCmdlet.ParameterSetName, (Get-Variable -Name $PSCmdlet.ParameterSetName).Value
    Get-CimInstance -Query $Query @CimParams | Select-Object -Property @(
        @{Label="Name";Expression={$_.LocalizedDisplayName}}
        @{Label="Description";Expression={$_.LocalizedDescription}}
        @{Label="ObjectType";Expression={[SMS_ConfigurationItemLatest_CIType_ID]$_.CIType_ID}}
        "CI_ID"
    )
}

function Find-CMDeploymentType {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ParameterSetName="ModelName")]
        [String]$ModelName,
        [Parameter(Mandatory, ParameterSetName="CI_ID")]
        [String]$CI_ID,
        [Parameter(Mandatory)]
        [Hashtable]$CimParams
    )
    $Query = "SELECT AppModelName,CI_ID,LocalizedDisplayName FROM SMS_DeploymentType WHERE IsLatest = 'True' AND {0} = '{1}'" -f $PSCmdlet.ParameterSetNAme, (Get-Variable -Name $PSCmdlet.ParameterSetName).Value
    Get-CimInstance -Query $Query @CimParams | Select-Object -Property @(
        @{Label="Name";Expression={$_.LocalizedDisplayName}}
        @{Label="Description";Expression={$_.LocalizedDescription}}
        @{Label="ObjectType";Expression={"DeploymentType"}}
        "CI_ID"
        @{Label="AppCIID";Expression={ConvertTo-ModelNameCIID -ModelName $_.AppModelName -SiteServer $SiteServer -SiteCode $SiteCode}}
    )
}

function Find-CMDriver {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ParameterSetName="ModelName")]
        [String]$ModelName,
        [Parameter(Mandatory, ParameterSetName="CI_ID")]
        [String]$CI_ID,
        [Parameter(Mandatory)]
        [Hashtable]$CimParams
    )
    $Query = "SELECT CI_ID,LocalizedDisplayName FROM SMS_Driver WHERE {0} = '{1}'" -f $PSCmdlet.ParameterSetNAme, (Get-Variable -Name $PSCmdlet.ParameterSetName).Value
    Get-Ciminstance -Query $Query @CimParams | Select-Object -Property @(
        @{Label="Name";Expression={$_.LocalizedDisplayName}}
        @{Label="Description";Expression={$_.LocalizedDescription}}
        @{Label="ObjectType";Expression={"Driver"}}
        "CI_ID"
    )
}

function Invoke-NativeCommand {
    <#
    .SYNOPSIS
        Invoke a native command (.exe) as a new process.
        
    .DESCRIPTION
        Invoke-NativeCommand executes an arbitrary executable as a new process. Both the standard
        and error output streams are redirected.
        
        Error out is written as a single non-terminating error. ErrorAction can be used to raise
        this as a terminating error.
    
    .EXAMPLE
        Invoke-NativeCommand git clone repo-uri -ErrorAction "Stop"
        
        Run the git command to clone repo-uri. Raise a terminating error if the command fails.
    #>


    [CmdletBinding()]
    param (
        <#
            The command line to execute. This parameter is named to attempt to avoid conflicts with
            parameters for the executing command line.
        #>

        [Parameter(Position = 1, ValueFromRemainingArguments, ValueFromPipeline)]
        $__CommandLine
    )

    process {
        $command, $argumentList = $__CommandLine

        try {
            $process = [System.Diagnostics.Process]@{
                StartInfo = [System.Diagnostics.ProcessStartInfo]@{
                    FileName               = (Get-Command $command -ErrorAction "Stop").Source
                    Arguments              = $argumentList
                    WorkingDirectory       = $pwd
                    RedirectStandardOutput = $true
                    RedirectStandardError  = $true
                    UseShellExecute        = $false
                }
            }
            $null = $process.Start()
            $process.WaitForExit()

            while (-not $process.StandardOutput.EndOfStream) {
                $process.StandardOutput.ReadToEnd()
            }

            while (-not $process.StandardError.EndOfStream) {
                Write-Error $process.StandardError.ReadToEnd()
            }
        } catch {
            Write-Error -ErrorRecord $_
        }
    }
}

function Resolve-DP {
    <#
    .SYNOPSIS
        Validate whether a given host is a distribution point within a Configuration Manager site
    .DESCRIPTION
        Validate whether a given host is a distribution point within a Configuration Manager site
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [String]$Name,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer,
        
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode
    )
    begin {
        $OriginalLocation = (Get-Location).Path

        if($null -eq (Get-PSDrive -Name $SiteCode -PSProvider "CMSite" -ErrorAction "SilentlyContinue")) {
            $null = New-PSDrive -Name $SiteCode -PSProvider "CMSite" -Root $SiteServer -ErrorAction "Stop"
        }

        Set-Location ("{0}:\" -f $SiteCode) -ErrorAction "Stop"
    }
    process {
        try {
            $Obj = Get-CMDistributionPoint -Name $Name -AllSite -ErrorAction "Stop"
            if (-not $Obj) {
                throw ("Distribution point '{0}' does not exist" -f $Name)
            }
        }
        catch {
            Set-Location $OriginalLocation 
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
        Set-Location $OriginalLocation   
    }
}

function Resolve-DPGroup {
    <#
    .SYNOPSIS
        Validate whether a distribution point group exists within a Configuration Manager site
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [String]$Name,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer,
        
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode
    )
    begin {
        $OriginalLocation = (Get-Location).Path

        if($null -eq (Get-PSDrive -Name $SiteCode -PSProvider "CMSite" -ErrorAction "SilentlyContinue")) {
            $null = New-PSDrive -Name $SiteCode -PSProvider "CMSite" -Root $SiteServer -ErrorAction "Stop"
        }

        Set-Location ("{0}:\" -f $SiteCode) -ErrorAction "Stop"
    }
    process {
        try {
            $Obj = Get-CMDistributionPointGroup -Name $Name -ErrorAction "Stop"
            if (-not $Obj) {
                throw ("Distribution point group '{0}' does not exist" -f $Name)
            }
        }
        catch {
            Set-Location $OriginalLocation 
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
        Set-Location $OriginalLocation   
    }
}
#endregion

#region Public functions
function Compare-DPContent {
    <#
    .SYNOPSIS
        Returns a list of content objects missing from the given target server compared to the source server.
    .PARAMETER Source
        Name of the referencing distribution point (as it appears in ConfigMgr, usually FQDN) you want to query.
    .PARAMETER Target
        Name of the differencing distribution point (as it appears in ConfigMgr, usually FQDN) you want to query.
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.

        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.

        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.
        
        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Compare-DPContent -Source "dp1.contoso.com" -Target "dp2.contoso.com"

        Return content objects which are missing from "dp2.contoso.com" compared to "dp1.contoso.com".
    .EXAMPLE
        PS C:\> Compare-DPContent -Source "dp1.contoso.com" -Target "dp2.contoso.com" | Start-DPContentDistribution -DistributionPoint "dp2.contoso.com"

        Compares the missing content objects on "dp2.contoso.com" compared to "dp1.contoso.com", and distributes them to "dp2.contoso.com"
    .EXAMPLE
        PS C:\> Compare-DPContent -Source "dp1.contoso.com" -Target "dp2.contoso.com" | Remove-DPContent

        Compares the missing content objects in "dp2.contoso.com" compared to "dp1.contoso.com", and removes them from distribution point "dp1.contoso.com".

        Use -DistributionPoint with Remove-DPContent to either explicitly target "dp1.contoso.com" or some other group. In this example, "dp1.contoso.com" is the implicit target distribution point group as it reads the DistributionPointGroup property return from Compare-DPGroupContent.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$Source,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$Target,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }

        try {
            Resolve-DP -Name $Source -SiteServer $SiteServer -SiteCode $SiteCode
            Resolve-DP -Name $Target -SiteServer $SiteServer -SiteCode $SiteCode
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    process {
        $SourceContent = Get-DPContent -DistributionPoint $Source -SiteServer $SiteServer -SiteCode $SiteCode
        $TargetContent = Get-DPContent -DistributionPoint $Target -SiteServer $SiteServer -SiteCode $SiteCode
    
        Compare-Object -ReferenceObject @($SourceContent) -DifferenceObject @($TargetContent) -Property ObjectID -PassThru | ForEach-Object {
            if ($_.SideIndicator -eq "<=") {
                [PSCustomObject]@{
                    PSTypeName        = "PSCMContentMgmt"
                    ObjectName        = $_.ObjectName
                    Description       = $_.Description
                    ObjectType        = [SMS_DPContentInfo]$_.ObjectType
                    ObjectID          = $_.ObjectID
                    SourceSize        = $_.SourceSize
                    DistributionPoint = $_.DistributionPoint
                }  
            }
        }
    }
    end {
    }    
}

function Compare-DPGroupContent {
    <#
    .SYNOPSIS
        Returns a list of content objects missing from the given target server compared to the source server.
    .PARAMETER Source
        Name of the referencing distribution point group you want to query.
    .PARAMETER Target
        Name of the differencing distribution point group you want to query.
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.

        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.

        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.
        
        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Compare-DPGroupContent -Source "Asia DPs" -Target "Europe DPs"

        Return content objects which are missing from "Europe DPs" compared to "Asia DPs"
    .EXAMPLE
        PS C:\> Compare-DPGroupContent -Source "London DPs" -Target "Mancester DPs" | Start-DPGroupContentDistribution -DistributionPointGroup "Mancester DPs"

        Compares the missing content objects in group Manchester DPs compared to "London DPs", and distributes them to distribution point group Manchester DPs.
    .EXAMPLE
        PS C:\> Compare-DPGroupContent -Source "London DPs" -Target "Mancester DPs" | Remove-DPGroupContent

        Compares the missing content objects in group Manchester DPs compared to "London DPs", and removes them from distribution point group "London DPs".

        Use -DistributionPointGroup with Remove-DPGroupContent to either explicitly target "London DPs" or some other group. In this example, "London DPs" is the implicit target distribution point group as it reads the DistributionPointGroup property return from Compare-DPGroupContent.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$Source,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$Target,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }

        try {
            Resolve-DPGroup -Name $Source -SiteServer $SiteServer -SiteCode $SiteCode
            Resolve-DPGroup -Name $Target -SiteServer $SiteServer -SiteCode $SiteCode
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    process {
        $SourceContent = Get-DPGroupContent -DistributionPointGroup $Source -SiteServer $SiteServer -SiteCode $SiteCode
        $TargetContent = Get-DPGroupContent -DistributionPointGroup $Target -SiteServer $SiteServer -SiteCode $SiteCode
    
        Compare-Object -ReferenceObject @($SourceContent) -DifferenceObject @($TargetContent) -Property ObjectID -PassThru | ForEach-Object {
            if ($_.SideIndicator -eq "<=") {
                [PSCustomObject]@{
                    PSTypeName             = "PSCMContentMgmt"
                    ObjectName             = $_.ObjectName
                    Description            = $_.Description
                    ObjectType             = [SMS_DPContentInfo]$_.ObjectType
                    ObjectID               = $_.ObjectID
                    SourceSize             = $_.SourceSize
                    DistributionPointGroup = $_.DistributionPointGroup
                }  
            }
        }
    }
    end {
    }    
}

function Export-DPContent {
    <#
    .SYNOPSIS
        Exports distribution point content to .pkgx files
    .PARAMETER InputObject
        A PSObject type "PSCMContentMgmt" generated by Get-DPContent
    .PARAMETER DistributionPoint
        Name of distribution point (as it appears in ConfigMgr, usually FQDN) you want to export content from.
    .PARAMETER ObjectID
        Unique ID of the content object you want to export.

        For Applications the ID must be the CI_ID value whereas for all other content objects the ID is PackageID.

        When using this parameter you must also use ObjectType.
    .PARAMETER ObjectType
        Object type of the content object you want to export.

        Can be one of the following values: "Package", "DriverPackage", "DeploymentPackage", "OperatingSystemImage", "OperatingSystemInstaller", "BootImage", "Application".

        When using this parameter you must also use ObjectID.
    .PARAMETER Folder
        The target directory to store the generated .pkgx files in.
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.

        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.

        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.

        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Get-DPContent -DistributionPoint "dp1.contoos.com" | Export-DPContent -Folder "E:\prestaged"

        Gathers all content objects on "dp1.contoso.com" and exports them to .pkgx files in E:\prestaged, overwriting any files that already exist with the same name.
    .EXAMPLE
        PS C:\> Compare-DPContent -Source "dp1.contoso.com" -Target "dp2.contoso.com" | Export-DPContent -Folder "E:\prestaged"

        Compares the missing content objects on "dp2.contoso.com" compared to "dp1.contoso.com", and exports them to "E:\prestaged".
    .EXAMPLE
        PS C:\> Export-DPContent -DistributionPoint "dp1.contoso.com" -ObjectID "P01000F6" -ObjectType "Package" -Folder "E:\prestaged"

        Exports package item P01000F6 from dp1.contoos.com and saves the exported .pkgx file in E:\prestaged.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low")]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName="InputObject")]
        [PSTypeName('PSCMContentMgmt')]
        [PSCustomObject]$InputObject,

        [Parameter(Mandatory, ParameterSetName="SpecifyProperties")]
        [ValidateNotNullOrEmpty()]
        [String]$ObjectID,

        [Parameter(Mandatory, ParameterSetName="SpecifyProperties")]
        [ValidateSet("Package","DriverPackage","DeploymentPackage","OperatingSystemImage","OperatingSystemInstaller","BootImage","Application")]
        [SMS_DPContentInfo]$ObjectType,

        [Parameter(Mandatory)]
        [ValidateScript({
            if (!([System.IO.Directory]::Exists($_))) {
                throw "Invalid path or access denied"
            } elseif (!($_ | Test-Path -PathType Container)) {
                throw "Value must be a directory, not a file"
            } else {
                return $true
            }
        })]
        [String]$Folder,

        [Parameter(Mandatory, ParameterSetName="SpecifyProperties")]
        [ValidateNotNullOrEmpty()]
        [String]$DistributionPoint,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }

        $TargetDP = $DistributionPoint

        if ($PSCmdlet.ParameterSetName -ne "InputObject") {
            $InputObject = [PSCustomObject]@{
                ObjectID          = $ObjectID
                ObjectType        = $ObjectType
                DistributionPoint = $TargetDP
            }
        }

        $OriginalLocation = (Get-Location).Path

        if($null -eq (Get-PSDrive -Name $SiteCode -PSProvider "CMSite" -ErrorAction "SilentlyContinue")) {
            $null = New-PSDrive -Name $SiteCode -PSProvider "CMSite" -Root $SiteServer -ErrorAction "Stop"
        }

        Set-Location ("{0}:\" -f $SiteCode) -ErrorAction "Stop"
    }
    process {
        try {
            foreach ($Object in $InputObject) {
                switch ($true) {
                    ($LastDP -ne $Object.DistributionPoint -And -not $PSBoundParameters.ContainsKey("DistributionPoint")) {
                        $TargetDP = $Object.DistributionPoint
                    }
                    ($LastDP -ne $TargetDP) {
                        try {
                            Resolve-DP -Name $TargetDP -SiteServer $SiteServer -SiteCode $SiteCode
                        }
                        catch {
                            Write-Error -ErrorRecord $_
                            return
                        }
                        
                        $LastDP = $TargetDP
                    }
                    default {
                        $LastDP = $TargetDP
                    }
                }

                $File = "{0}_{1}.pkgx" -f [int]$Object.ObjectType, $Object.ObjectID
                $Path = Join-Path -Path $Folder -ChildPath $File
        
                $result = @{ 
                    PSTypeName = "PSCMContentMgmtPrestage"
                    ObjectID   = $Object.ObjectID
                    ObjectType = $Object.ObjectType
                    Message    = $null
                }
        
                $Command = 'Publish-CMPrestageContent -{0} "{1}" -DistributionPointName "{2}" -FileName "{3}"' -f [SMS_DPContentInfo_CMParameters][SMS_DPContentInfo]$Object.ObjectType, $Object.ObjectID, $TargetDP, $Path
                $ScriptBlock = [ScriptBlock]::Create($Command)
                try {
                    if ($PSCmdlet.ShouldProcess(
                        ("Would export '{0}' ({1}) to '{2}'" -f $Object.ObjectID, $Object.ObjectType, $Folder),
                        "Are you sure you want to continue?",
                        ("Exporting '{0}' ({1}) to '{2}'" -f $Object.ObjectID, $Object.ObjectType, $Folder))) {
                            Invoke-Command -ScriptBlock $ScriptBlock -ErrorAction "Stop"
                            $result["Result"] = "Success"
                    }
                    else {
                        $result["Result"] = "No change"
                    }
                }
                catch {
                    Write-Error -ErrorRecord $_
                    $result["Result"] = "Failed"
                    $result["Message"] = $_.Exception.Message
                }
                
                if (-not $WhatIfPreference) { [PSCustomObject]$result }
            }
        }
        catch {
            Set-Location $OriginalLocation 
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
        Set-Location $OriginalLocation
    }
}

function Find-CMOBject {
    <#
    .SYNOPSIS
        A "searcher" function to find Configuration Manager objects which match a given ID.
    .DESCRIPTION
        A "searcher" function to find Configuration Manager objects which match a given ID. The ID can be anything - the function will attempt to determine to ID type based on its structure using regex, and looking for objects based on its predicted type.
        
        The function searches for the following objects:
            - Applications
            - Deployment Types
            - Packages
            - Drivers
            - Driver Packages
            - Boot Images
            - Operating System Images
            - Operating System Upgrade Images
            - Task Sequences
            - Configuration Items
            - Configuration Baselines
            - User Collections
            - Device Collections
            - (Software Update) Deployment Packages
    .EXAMPLE
        PS C:\> Find-CMObject -ID "ACC00048"

        Finds any object which has the PackageID "ACC00048", this includes applications, collections, driver packages, boot images, OS images, OS upgrade images, task sequences and deployment packages.
    .EXAMPLE
        PS C:\> Find-CMObject -ID "17007122"

        Finds any object which has the CI_ID "17007122", this includes applications, deployment types, drivers, configuration items and configuration baselines.
    .EXAMPLE
        PS C:\> Find-CMObject -ID "ScopeId_B3FF3CC4-0319-4434-9D24-77689C53C615/Application_197d8de7-022d-4c0b-aec4-c339ccc17ba4"

        Finds an application which matches the ModelName "ScopeId_B3FF3CC4-0319-4434-9D24-77689C53C615/Application_197d8de7-022d-4c0b-aec4-c339ccc17ba4"
    .EXAMPLE
        PS C:\> Find-CMObject -ID "ScopeId_B3FF3CC4-0319-4434-9D24-77689C53C615/DeploymentType_328afa1b-6fdb-4f13-8133-f97aab8edff2"

        Find a deployment type which matches the ModelName "ScopeId_B3FF3CC4-0319-4434-9D24-77689C53C615/DeploymentType_328afa1b-6fdb-4f13-8133-f97aab8edff2"
    .EXAMPLE
        PS C:\> Find-CMObject -ID "ScopeId_B3FF3CC4-0319-4434-9D24-77689C53C615/Baseline_0fc5de89-80c9-4a0e-8f92-7a3a99cfe747"

        Finds a configuration baseline which matches the ModelName "ScopeId_B3FF3CC4-0319-4434-9D24-77689C53C615/Baseline_0fc5de89-80c9-4a0e-8f92-7a3a99cfe747"
    .EXAMPLE
        PS C:\> Find-CMObject -ID "ScopeId_B3FF3CC4-0319-4434-9D24-77689C53C615/LogicalName_3a7dc9c1-3bd1-4cc3-b750-30cc9debe1ec"

        Finds a configuration item which matches the ModelName "ScopeId_B3FF3CC4-0319-4434-9D24-77689C53C615/LogicalName_3a7dc9c1-3bd1-4cc3-b750-30cc9debe1ec"
    .EXAMPLE
        PS C:\> Find-CMOBject -ID "SCOPEID_B3FF3CC4-0319-4434-9D24-77689C53C615/DRIVER_4E2772AE8A92D353896D69ECCA435728C4B44957_180B604588D114D354CFF75148B012319F39A8EB8F7C5AB10C21084AEA14F0D5"
        
        Finds a driver which matches the ModelName "SCOPEID_B3FF3CC4-0319-4434-9D24-77689C53C615/DRIVER_4E2772AE8A92D353896D69ECCA435728C4B44957_180B604588D114D354CFF75148B012319F39A8EB8F7C5AB10C21084AEA14F0D5"
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String[]]$ID,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }

        $GetCimInstanceSplat = @{
            ComputerName    = $SiteServer
            Namespace       = "ROOT/SMS/Site_{0}" -f $SiteCode 
        }
    }
    process {
        :parent switch -Regex ($ID) {
            "^ScopeId_[\w-]+\/Application_[\w-]+$" { # ModelName for application
                Find-CMApplication -ModelName $_ -CimParams $GetCimInstanceSplat
            }
            "^ScopeId_[\w-]+\/DeploymentType_[\w-]+$" { # ModelName for deployment type
                Find-CMDeploymentType -ModelName $_ -CimParams $GetCimInstanceSplat
            }
            "^ScopeId_[\w-]+\/DRIVER_[\w_]+$" { # ModelName for drivers
                Find-CMDriver -ModelName $_ -CimParams $GetCimInstanceSplat
            }
            "^ScopeId_[\w-]+\/(LogicalName|Baseline)_[\w-]+$" { # ModelName for CI or CB
                Find-CMCICB -ModelName $_ -CimParams $GetCimInstanceSplat
            }
            "^[0-9]{8}$" { # CI_ID for CI/CB, application, deployment type or driver
                $r = Find-CMCICB -CI_ID $_ -CimParams $GetCimInstanceSplat
                if ($r -is [Object]) { $r; continue parent }
                $r = Find-CMApplication -CI_ID $_ -CimParams $GetCimInstanceSplat
                if ($r -is [Object]) { $r; continue parent }
                $r = Find-CMDeploymentType -CI_ID $_ -CimParams $GetCimInstanceSplat
                if ($r -is [Object]) { $r; continue parent }
                $r = Find-CMDriver -CI_ID $_ -CimParams $GetCimInstanceSplat
                if ($r -is [Object]) { $r; continue parent }
            }
            ("^({0}|SMS)(\w){{5}}$" -f $SiteCode) { # PackageID (or IDs of similar structure, e.g. collections) for each of the objects listed in the $Classes array below
                $ObjectId = $_

                $Classes = @(
                    "SMS_Package"
                    "SMS_DriverPackage"
                    "SMS_ImagePackage"
                    "SMS_OperatingSystemInstallPackage"
                    "SMS_BootImagePackage"
                    "SMS_SoftwareUpdatesPackage"
                    "SMS_TaskSequencePackage"
                    "SMS_Collection"
                    "SMS_DeploymentSummary"
                    "SMS_ApplicationLatest"
                )
                
                switch ($Classes) {
                    "SMS_ApplicationLatest" {
                        # This class is deliberately last in the array because it's the most taxing
                        # To retrieve an application's PackageID, we must first gather all applications
                        # and invoke Get-CimInstance again on each application CIM object to get the PackageID property because it's a lazy property
                        $Query = "SELECT * FROM {0}" -f $_
                        $AllApplications = Get-CimInstance -Query $Query @GetCimInstanceSplat
                        
                        foreach ($Application in $AllApplications) {
                            $Properties = $Application | Get-CimInstance
                            
                            if ($Properties.PackageID -eq $ObjectId) {
                                $Application | Select-Object -Property @(
                                    @{Label="Name";Expression={$_.LocalizedDisplayName}}
                                    @{Label="Description";Expression={$_.LocalizedDescription}}
                                    @{Label="ObjectType";Expression={"Application"}}
                                    "CI_ID"
                                )
                                continue parent
                            }
                        }
                    }
                    "SMS_Collection" {
                        $Query = "SELECT Name, CollectionID, Comment, CollectionType FROM {0} WHERE CollectionID = '{1}'" -f $_, $ObjectId

                        Get-CimInstance -Query $Query @GetCimInstanceSplat | Select-Object -Property @(
                            "Name",
                            @{Label="Description";Expression={$_.Comment}}
                            @{Label="ObjectType";Expression={[SMS_Collection]$_.CollectionType}}
                            "CollectionID"
                        )
                    }
                    "SMS_DeploymentSummary" {
                        $Query = "SELECT ApplicationName, DeploymentID, CollectionID, CollectionName, PackageID FROM {0} WHERE DeploymentID = '{1}'" -f $_, $ObjectId

                        Get-CimInstance -Query $Query @GetCimInstanceSplat | Select-Object -Property @(
                            @{Label="Name";Expression={"Deployment ID of '{0}' ({1})" -f $_.ApplicationName, $_.PackageId}}
                            @{Label="Description";Expression={"Deployed to '{0}' ({1})" -f $_.CollectionName, $_.CollectionId}}
                            @{Label="ObjectType";Expression={"DeploymentID"}}
                            "DeploymentId"
                        )
                    }
                    default {
                        $Query = "SELECT PackageID, Name, Description, PackageType FROM {0} WHERE PackageID = '{1}'" -f $_, $ObjectId
                        
                        $result = Get-Ciminstance -Query $Query @GetCimInstanceSplat | Select-Object -Property @(
                            "Name"
                            "Description"
                            @{Label="ObjectType";Expression={[SMS_DPContentInfo]$_.PackageType}}
                            "PackageID"
                        )

                        if ($result -is [Object]) {        
                            $result    
                            continue parent    
                        }
                    }
                }
            }
            default {
                # Write-Warning ("Can not determine what type of object used for '{0}'" -f $_)
            }
        }
    }
    end {
    }
}

function Get-DPContent {
    <#
    .SYNOPSIS
        Get all content distributed to a given distribution point by querying SMS_DPContentInfo class.
    .DESCRIPTION
        Get all content distributed to a given distribution point by querying SMS_DPContentInfo class.

        By default this function returns all content object types that match the given distribution point in the SMS_DPContentInfo class on the site server.

        You can filter the content objects by cumulatively using the available switches, e.g. using -Package -DriverPackage will return packages and driver packages.

        Properties returned are: ObjectName, Description, ObjectType, ObjectID, SourceSize, DistributionPoint.
    .PARAMETER Name
        Name of distribution point (as it appears in ConfigMgr, usually FQDN) you want to query.
    .PARAMETER Package
        Filter on packages
    .PARAMETER DriverPackage
        Filter on driver packages
    .PARAMETER DeploymentPackage
        Filter on deployment packages
    .PARAMETER OperatingSystemImage
        Filter on Operating System images
    .PARAMETER OperatingSystemInstaller
        Filter on Operating System upgrade images
    .PARAMETER BootImage
        Filter on boot images
    .PARAMETER Application
        Filter on applications
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.
        
        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.
        
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.
        
        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Get-DPContent -Name dp.contoso.com -Package -Application

        Return all packages and applications found on dp.contoso.com.s
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$DistributionPoint,

        [Parameter()]
        [Switch]$Package,

        [Parameter()]
        [Switch]$DriverPackage,
        
        [Parameter()]
        [Switch]$DeploymentPackage,
        
        [Parameter()]
        [Switch]$OperatingSystemImage,
        
        [Parameter()]
        [Switch]$OperatingSystemInstaller,
        
        [Parameter()]
        [Switch]$BootImage,
        
        [Parameter()]
        [Switch]$Application,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }
        
        try {
            Resolve-DP -Name $DistributionPoint -SiteServer $SiteServer -SiteCode $SiteCode
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    process {
        $Namespace = "ROOT/SMS/Site_{0}" -f $SiteCode
        $Query = "SELECT * FROM SMS_DPContentInfo WHERE NALPath like '%{0}%'" -f $DistributionPoint
    
        $conditions = switch ($true) {
            $Package                    { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"Package" }
            $DriverPackage              { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"DriverPackage" }
            $DeploymentPackage          { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"DeploymentPackage" }
            $OperatingSystemImage       { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"OperatingSystemImage" }
            $OperatingSystemInstaller   { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"OperatingSystemInstaller" }
            $BootImage                  { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"BootImage" }
            $Application                { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"Application" }
        }
    
        if ($conditions) { 
            $Query = "{0} AND ( {1} )" -f $Query, ([String]::Join(" OR ", $conditions)) 
        }
    
        Get-CimInstance -ComputerName $SiteServer -Namespace $Namespace -Query $Query -ErrorAction "Stop" | ForEach-Object {
            [PSCustomObject]@{
                PSTypeName        = "PSCMContentMgmt"
                ObjectName        = $_.Name
                Description       = $_.Description
                ObjectType        = [SMS_DPContentInfo]$_.ObjectType
                ObjectID          = $(if ($_.ObjectType -eq [SMS_DPContentInfo]"Application") {
                    ConvertTo-ModelNameCIID -ModelName $_.ObjectID -SiteServer $SiteServer -SiteCode $SiteCode
                }
                else {
                    $_.ObjectID
                })
                SourceSize        = $_.SourceSize
                DistributionPoint = $DistributionPoint
            }
        }
    }
    end {
    }
}

function Get-DPDistributionStatus {
    <#
    .SYNOPSIS
        Retrieve the content distribution status of all objects for a distribution point.
    .PARAMETER DistributionPoint
        Name of distribution point (as it appears in ConfigMgr, usually FQDN) you want to query.
    .PARAMETER Distributed
        Filter on objects in distributed state
    .PARAMETER DistributionPending
        Filter on objects in distribution pending state
    .PARAMETER DistributionRetrying
        Filter on objects in distribution retrying state
    .PARAMETER DistributionFailed
        Filter on objects in distribution failed state
    .PARAMETER RemovalPending
        Filter on objects in removal pending state
    .PARAMETER RemovalRetrying
        Filter on objects in removal retrying state
    .PARAMETER RemovalFailed
        Filter on objects in removal failed state
    .PARAMETER ContentUpdating
        Filter on objects in content updating state
    .PARAMETER ContentMonitoring
        Filter on objects in content monitoring state
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.

        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.

        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.

        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Get-DPDistributionStatus -DistributionPoint "dp1.contoso.com"

        Gets the content distribution status for all objects on "dp1.contoso.com".
    #>

    [CmdletBinding()]
    param (
        [ParameteR(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$DistributionPoint,

        [Parameter()]
        [Switch]$Distributed,

        [Parameter()]
        [Switch]$DistributionPending,

        [Parameter()]
        [Switch]$DistributionRetrying,

        [Parameter()]
        [Switch]$DistributionFailed,

        [Parameter()]
        [Switch]$RemovalPending,

        [Parameter()]
        [Switch]$RemovalRetrying,

        [Parameter()]
        [Switch]$RemovalFailed,

        [Parameter()]
        [Switch]$ContentUpdating,

        [Parameter()]
        [Switch]$ContentMonitoring,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }

        try {
            Resolve-DP -Name $DistributionPoint -SiteServer $SiteServer -SiteCode $SiteCode
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    process {
        $Namespace = "ROOT/SMS/Site_{0}" -f $SiteCode
        $Query = "SELECT PackageID,PackageType,State,SourceVersion FROM SMS_PackageStatusDistPointsSummarizer WHERE ServerNALPath like '%{0}%'" -f $DistributionPoint

        $conditions = switch ($true) {
            $Distributed            { "State = '{0}'" -f [Int][SMS_PackageStatusDistPointsSummarizer_State]"DISTRIBUTED" }
            $DistributionPending    { "State = '{0}'" -f [Int][SMS_PackageStatusDistPointsSummarizer_State]"DISTRIBUTION_PENDING" }
            $DistributionRetrying   { "State = '{0}'" -f [Int][SMS_PackageStatusDistPointsSummarizer_State]"DISTRIBUTION_RETRYING" }
            $DistributionFailed     { "State = '{0}'" -f [Int][SMS_PackageStatusDistPointsSummarizer_State]"DISTRIBUTION_FAILED" }
            $RemovalPending         { "State = '{0}'" -f [Int][SMS_PackageStatusDistPointsSummarizer_State]"REMOVAL_PENDING" }
            $RemovalRetrying        { "State = '{0}'" -f [Int][SMS_PackageStatusDistPointsSummarizer_State]"REMOVAL_RETRYING" }
            $RemovalFailed          { "State = '{0}'" -f [Int][SMS_PackageStatusDistPointsSummarizer_State]"REMOVAL_FAILED" }
            $ContentUpdating        { "State = '{0}'" -f [Int][SMS_PackageStatusDistPointsSummarizer_State]"CONTENT_UPDATING" }
            $ContentMonitoring      { "State = '{0}'" -f [Int][SMS_PackageStatusDistPointsSummarizer_State]"CONTENT_MONITORING" }
        }

        if ($conditions) {
            $Query = "{0} AND ( {1} )" -f $Query, ([String]::Join(" OR ", $conditions)) 
        }

        Get-CimInstance -ComputerName $SiteServer -Namespace $Namespace -Query $Query -ErrorAction "Stop" | ForEach-Object {
            [PSCustomObject]@{
                PSTypeName        = "PSCMContentMgmt"
                ObjectID          = $_.PackageID
                ObjectType        = [SMS_PackageStatusDistPointsSummarizer_PackageType]$_.PackageType
                State             = [SMS_PackageStatusDistPointsSummarizer_State]$_.State
                SourceVersion     = $_.SourceVersion
                DistributionPoint = $DistributionPoint
            }
        }
    }
    end {
    }
}

function Get-DPGroupContent {
    <#
    .SYNOPSIS
        Get all content distributed to a given distribution point group by querying the SMS_DPGroupContentInfo.
    .DESCRIPTION
        Get all content distributed to a given distribution point group by querying the SMS_DPGroupContentInfo.

        By default this function returns all content object types that match the given distribution point group in the SMS_DPGroupContentInfo class on the site server.

        You can filter the content objects by cumulatively using the available switches, e.g. using -Package -DriverPackage will return packages and driver packages.

        Properties returned are: ObjectName, Description, ObjectType, ObjectID, SourceSize, DistributionPoint.
    .PARAMETER Name
        Name of distribution point group you want to query.
    .PARAMETER Package
        Filter on packages
    .PARAMETER DriverPackage
        Filter on driver packages
    .PARAMETER DeploymentPackage
        Filter on deployment packages
    .PARAMETER OperatingSystemImage
        Filter on Operating System images
    .PARAMETER OperatingSystemInstaller
        Filter on Operating System upgrade images
    .PARAMETER BootImage
        Filter on boot images
    .PARAMETER Application
        Filter on applications
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.
        
        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.
        
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.
        
        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Get-DPGroupContent -Name "Asia DPs" -Package -Application

        Return all packages and applications found in the distribution point group "Asia DPs"
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$DistributionPointGroup,

        [Parameter()]
        [Switch]$Package,

        [Parameter()]
        [Switch]$DriverPackage,
        
        [Parameter()]
        [Switch]$DeploymentPackage,
        
        [Parameter()]
        [Switch]$OperatingSystemImage,
        
        [Parameter()]
        [Switch]$OperatingSystemInstaller,
        
        [Parameter()]
        [Switch]$BootImage,
        
        [Parameter()]
        [Switch]$Application,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }
        
        try {
            Resolve-DPGroup -Name $DistributionPointGroup -SiteServer $SiteServer -SiteCode $SiteCode
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }        
    }
    process {
        $Namespace = "ROOT/SMS/Site_{0}" -f $SiteCode
        $Query = "SELECT *
        FROM SMS_DPGroupContentInfo
        WHERE SMS_DPGroupContentInfo.GroupID in (
            SELECT SMS_DPGroupInfo.GroupID
            FROM SMS_DPGroupInfo
            WHERE Name = '{0}'
        )"
 -f $DistributionPointGroup
    
        $conditions = switch ($true) {
            $Package                    { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"Package" }
            $DriverPackage              { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"DriverPackage" }
            $DeploymentPackage          { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"DeploymentPackage" }
            $OperatingSystemImage       { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"OperatingSystemImage" }
            $OperatingSystemInstaller   { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"OperatingSystemInstaller" }
            $BootImage                  { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"BootImage" }
            $Application                { "ObjectType = '{0}'" -f [Int][SMS_DPContentInfo]"Application" }
        }
    
        if ($conditions) { 
            $Query = "{0} AND ( {1} )" -f $Query, ([String]::Join(" OR ", $conditions)) 
        }
    
        Get-CimInstance -ComputerName $SiteServer -Namespace $Namespace -Query $Query -ErrorAction "Stop" | ForEach-Object {
            [PSCustomObject]@{
                PSTypeName             = "PSCMContentMgmt"
                ObjectName             = $_.Name
                Description            = $_.Description
                ObjectType             = [SMS_DPContentInfo]$_.ObjectType
                ObjectID               = $(if ($_.ObjectType -eq [SMS_DPContentInfo]"Application") {
                    ConvertTo-ModelNameCIID -ModelName $_.ObjectID -SiteServer $SiteServer -SiteCode $SiteCode
                }
                else {
                    $_.ObjectID
                })
                SourceSize             = $_.SourceSize
                DistributionPointGroup = $DistributionPointGroup
            }
        }
    }
    end {
    }
}

function Import-DPContent {
    <#
    .SYNOPSIS
        Imports .pkgx files to the local distribution point found in the given -Folder.

        Must be run locally to the distribution point you're importing content to, and run as administrator (ExtractContent.exe requirement).
    .DESCRIPTION
        Imports .pkgx files to the local distribution point found in the given -Folder.

        Must be run locally to the distribution point you're importing content to, and run as administrator (ExtractContent.exe requirement).

        By default, this function only imports objects which are in "pending" state in the SMS_PackageStatusDistPointsSummarizer class on the site server (in console, view objects' distribution state in Monitoring > Distribution Status > Content Status).
        
        For objects which are "pending", the function looks in the given -Folder for .pkgx files and attempts to import them by calling ExtractContent.exe with those files.
        
        The .pkgx files in -Folder must match the file name pattern of "<ObjectType>_<ObjectID>.pkgx". The Export-DPContent function generates .pkgx files in this format. For example:
            512_16873723.pkgx - an Application (512, as per SMS_DPContentInfo) with CI_ID value 16873723
            258_ACC00004.pkgx - a Boot Image (258, as per SMS_DPContentInfo) with PackageID value ACC00004
            0_ACC00007.pkgx - a Package (0, as per SMS_DPContentInfo) with PackageID value ACC00007
        
        For .pkgx file that do not match this pattern, they are skipped.
        
        For .pkgx files that do match the pattern, but are not in the "pending" state, they are also skipped. Use the -ImportAllFromFolder switch to always import all matching .pkgx files.

        When calling this function, you are prompted for confirmation whether you want to import content to local host. Suppress this with -Confirm:$false.
    .PARAMETER Folder
        Folder containing .pkgx files.
    .PARAMETER ExtractContentExe
        Absolute path to ExtractContent.exe.

        The function attempts to discover the location of this exe, however if it is unable to find it you will receive a terminating error and asked to use this parameter.
    .PARAMETER ImportAllFromFolder
        Import all .pkgx files found -Folder regardless as to whether the object is currently in pending state or not.
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.
        
        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.
        
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.
        
        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Import-DPContent -Folder "F:\prestaged" -WhatIf

        Imports .pkgx files found in F:\prestaged but only if the objects are in "pending" state.
    .EXAMPLE
        PS C:\> Import-DPContent -Folder "\\server\share\prestaged" -ImportAllFromFolder -WhatIf

        Imports all .pkgx files found in \\server\share\prestaged.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
    param (
        [Parameter(Mandatory)]
        [ValidateScript({
            if (!([System.IO.Directory]::Exists($_))) {
                throw "Invalid path or access denied"
            } elseif (!($_ | Test-Path -PathType Container)) {
                throw "Value must be a directory, not a file"
            } else {
                return $true
            }
        })]
        [String]$Folder,

        [Parameter()]
        [ValidateScript({
            if (([System.IO.File]::Exists($_) -And ($_ -like "*ExtractContent.exe"))) {
                return $true
            } else {
                throw "Invalid path or given file is not named ExtractContent.exe"
            }
        })]
        [String]$ExtractContentExe,

        [Parameter()]
        [Switch]$ImportAllFromFolder,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        if (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") -eq $false) {
            $Exception = [UnauthorizedAccessException]::new("Must run as administrator")
            $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                $Exception,
                "2",
                [System.Management.Automation.ErrorCategory]::PermissionDenied,
                $null
            )
            $PSCmdlet.ThrowTerminatingError($ErrorRecord)
        }

        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }

        $DistributionPoint = [System.Net.Dns]::GetHostByName($env:ComputerName).HostName        

        try {
            Resolve-DP -Name $DistributionPoint -SiteServer $SiteServer -SiteCode $SiteCode
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }

        # Get-PSDrive instead of Get-Volume because of UAC
        :loop foreach ($Volume in (Get-PSDrive -PSProvider "FileSystem")) {
            $Paths = @(
                "{0}SMS_DP$\sms\Tools\ExtractContent.exe" -f $Volume.Root
                "{0}SMS_DP$\ExtractContent.exe" -f $Volume.Root
            )

            foreach ($Path in $Paths) {
                try {
                    if (Test-Path $Path -ErrorAction "Stop") {
                        $ExtractContentExe = $Path
                        break loop
                    }
                }
                catch [System.UnauthorizedAccessException] {
                    Write-Error -Message ("Access denied finding ExtractContent.exe in {0}" -f (Split-Path -Parent $Path)) -Category "PermissionDenied" -CategoryTargetName $Path
                }
                catch {
                    Write-Error -ErrorRecord $_
                }
            }
        }

        if (-not $ExtractContentExe) {
            $Exception = [System.IO.FileNotFoundException]::new("Could not find ExtractContent.exe on disk, please use -ExtractContentExe parameter")
            $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                $Exception,
                "2",
                [System.Management.Automation.ErrorCategory]::ObjectNotFound,
                $null
            )
            $PSCmdlet.ThrowTerminatingError($ErrorRecord)
        }

        try {
            if ($ImportAllFromFolder.IsPresent -eq $true) {
                $Files = Get-ChildItem -Path $Folder -Filter "*.pkgx" -ErrorAction "Stop"
            }
            else {
                $Namespace = "ROOT/SMS/Site_{0}" -f $SiteCode
                $Filter = "ServerNALPath like '%{0}%'" -f $DistributionPoint
                $ObjPackagesPending = (Get-CimInstance -ComputerName $SiteServer -Namespace $Namespace -ClassName "SMS_PackageStatusDistPointsSummarizer" -Filter $Filter -ErrorAction "Stop").Where{ $_.State -ne 0 }
            }
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    process {
        if ($ImportAllFromFolder.IsPresent -eq $true) {
            foreach ($File in $Files) {
                if ($File.Name -match "^(?<ObjectType>0|3|5|257|258|259|512)_(?<ObjectID>[A-Za-z0-9]+)\.pkgx$") {

                    $result = @{ 
                        PSTypeName = "PSCMContentMgmtImport"
                        ObjectID   = $Matches.ObjectID
                        ObjectType = [SMS_DPContentInfo]$Matches.ObjectType
                        Message    = $null
                    }

                    try {
                        if ($PSCmdlet.ShouldProcess(
                            ("Would import {0} {1} ({2}) to '{3}'" -f [SMS_DPContentInfo]$Matches.ObjectType, $Matches.ObjectID, $File.Name, $env:ComputerName),
                            "Are you sure you want to continue?",
                            ("Warning: Importing {0} {1} ({2}) to '{3}'" -f [SMS_DPContentInfo]$Matches.ObjectType, $Matches.ObjectID, $File.Name, $env:ComputerName))) {
                                $null = Invoke-NativeCommand $ExtractContentExe /p:$($File.FullName) /F -ErrorAction "Stop"
                                $result["Result"] = "Success"
                        }
                        else {
                            $result["Result"] = "No change"
                        }   
                    }
                    catch {
                        Write-Error -ErrorRecord $_
                        $result["Result"] = "Failed"
                        $result["Message"] = $_.Exception.Message
                    }
                    
                    if (-not $WhatIfPreference) { [PSCustomObject]$result }
                }
                else {
                    Write-Warning ("File '{0}' is not identifiable, skipping" -f $File.Name)
                }
            }
        }
        else {
            foreach ($ObjPackage in $ObjPackagesPending) {
                # All of the object type values between SMS_DPContentInfo and SMS_PackageStatusDistPointsSummarizer are similar except for Application
                $ObjectType = ([SMS_DPContentInfo]([SMS_PackageStatusDistPointsSummarizer_PackageType]$ObjPackage.PackageType).ToString()).value__
    
                if ($ObjectType -eq [SMS_DPContentInfo]"Application") {
                    $ObjectID = ConvertTo-PackageIDCIID -PackageID $ObjPackage.PackageID -SiteServer $SiteServer -SiteCode $SiteCode
                }
                else {
                    $ObjectID = $ObjPackage.PackageID
                }
    
                $FileName = "{0}_{1}.pkgx" -f $ObjectType, $ObjectID
                $Path = Join-Path -Path $Folder -ChildPath $FileName
    
                if (Test-Path $Path) {
                    $result = @{ 
                        PSTypeName = "PSCMContentMgmtImport"
                        ObjectID   = $ObjectID
                        ObjectType = [SMS_DPContentInfo]$ObjectType
                        Message    = $null
                    }

                    try {
                        if ($PSCmdlet.ShouldProcess(
                            ("Would import {0} {1} ({2}) to '{3}'" -f [SMS_DPContentInfo]$ObjectType, $ObjectID, $FileName, $env:ComputerName),
                            "Are you sure you want to continue?",
                            ("Warning: Importing {0} {1} ({2}) to '{3}'" -f [SMS_DPContentInfo]$ObjectType, $ObjectID, $FileName, $env:ComputerName))) {
                                $null = Invoke-NativeCommand $ExtractContentExe /p:$Path /F -ErrorAction "Stop"
                                $result["Result"] = "Success"
                        }
                        else {
                            $result["Result"] = "No change"
                        }
                    }
                    catch {
                        Write-Error -ErrorRecord $_
                        $result["Result"] = "Failed"
                        $result["Message"] = $_.Exception.Message
                    }

                    if (-not $WhatIfPreference) { [PSCustomObject]$result }
                }
                else {
                    Write-Warning ("Could not find '{0}' ({1}) '{2}'" -f $ObjectID, [SMS_DPContentInfo]$ObjectType, $Path)
                }
            }
        }
    }
    end {
    }
}

function Invoke-DPContentLibraryCleanup {
    <#
    .SYNOPSIS
        Invoke the ContentLibraryCleanup.exe utility against a distribution point.
    .DESCRIPTION
        Invoke the ContentLibraryCleanup.exe utility against a distribution point.

        This is essentially just a wrapper for the binary.

        Worth noting that omitting the -Delete parameter is the equivilant of omitting the "/delete" parameter for the binary too. In other words, without -Delete it will just report on orphaned content and not delete it.
    .PARAMETER DistributionPoint
        Name of the distribution point (as it appears in ConfigMgr, usually FQDN) you want to clean up.
    .PARAMETER ContentLibraryCleanupExe
        Absolute path to ContentLibraryCleanup.exe.

        The function attempts to discover the location of this exe, however if it is unable to find it you will receive a terminating error and asked to use this parameter.
    .PARAMETER Delete
        Deletes orphaned content.
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.
        
        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.
        
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.
        
        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Invoke-DPContentLibraryCleanup.ps1 -DistributionPoint "dp1.contoso.com"

        Queries "dp1.contoso.com" for orphaned content. Because of the missing -Delete parameter, data will not be deleted.
    .EXAMPLE
        PS C:\> Invoke-DPContentLibraryCleanup.ps1 -DistributionPoint "dp1.contoso.com" -ContentLibraryCleanupExe "C:\Sources\ContentLibraryCleanup.exe" -Delete

        Deletes orphaned content on "dp1.contoso.com". Uses binary "C:\Sources\ContentLibraryCleanup.exe".
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$DistributionPoint,

        [Parameter()]
        [ValidateScript({
            if (([System.IO.File]::Exists($_) -And ($_ -like "*ContentLibraryCleanup.exe"))) {
                return $true
            } else {
                throw "Invalid path or given file is not named ContentLibraryCleanup.exe"
            }
        })]
        [String]$ContentLibraryCleanupExe,

        [Parameter()]
        [Switch]$Delete,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        if ($DistributionPoint.StartsWith($env:ComputerName)) {
            if (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") -eq $false) {
                $Exception = [UnauthorizedAccessException]::new("Must run as administrator")
                $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                    $Exception,
                    "2",
                    [System.Management.Automation.ErrorCategory]::PermissionDenied,
                    $null
                )
                $PSCmdlet.ThrowTerminatingError($ErrorRecord)
            }
        }

        try {
            Resolve-DP -Name $DistributionPoint -SiteServer $SiteServer -SiteCode $SiteCode
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }

        $Namespace = "ROOT/SMS/Site_{0}" -f $SiteCode
        $Query = "SELECT InstallDir FROM SMS_Site WHERE SiteCode = '{0}'" -f $SiteCode

        try {
            $SiteInstallPath = (Get-CimInstance -ComputerName $SiteServer -Namespace $Namespace -Query $Query -ErrorAction "Stop").InstallDir
        }
        catch {
            Write-Error -ErrorRecord $_
        }

        $Paths = @(
            "\\{0}\SMS_{1}\cd.latest\SMSSETUP\TOOLS\ContentLibraryCleanup\ContentLibraryCleanup.exe" -f $SiteServer, $SiteCode
            "{0}\cd.latest\SMSSETUP\TOOLS\ContentLibraryCleanup\ContentLibraryCleanup.exe" -f $SiteInstallPath
        )
        
        foreach ($Path in $Paths) {
            try {
                if (Test-Path $Path -ErrorAction "Stop") {
                    $ContentLibraryCleanupExe = $Path
                }
            }
            catch [System.UnauthorizedAccessException] {
                Write-Error -Message ("Access denied finding ContentLibraryCleanup.exe in {0}" -f (Split-Path -Parent $Path)) -Category "PermissionDenied" -CategoryTargetName $Path
            }
            catch {
                Write-Error -ErrorRecord $_
            }
        }

        if (-not $ContentLibraryCleanupExe) {
            $Exception = [System.IO.FileNotFoundException]::new("Could not find ContentLibraryCleanup.exe, please use -ContentLibraryCleanupExe parameter")
            $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                $Exception,
                "2",
                [System.Management.Automation.ErrorCategory]::ObjectNotFound,
                $null
            )
            $PSCmdlet.ThrowTerminatingError($ErrorRecord)
        }
    }
    process {
        if ($Delete.IsPresent) {
            if ($PSCmdlet.ShouldProcess(
                ("Would perform content library cleanup on '{0}'" -f $DistributionPoint),
                "Are you sure you want to continue?",
                ("Warning: calling ContentLibraryCleanup.exe against '{0}' with /delete parameter" -f $DistributionPoint))) {
                    $pArgs = "/dp", $DistributionPoint, "/q", "/delete"
                    & $Path $pArgs
            }
        }
        else {
            $pArgs = "/dp", $DistributionPoint, "/q" 
            & $Path $pArgs
        }
    }
    end {
    }
}

function Remove-DPContent {
    <#
    .SYNOPSIS
        Remove objects from a distribution point
    .PARAMETER InputObject
        A PSObject type "PSCMContentMgmt" generated by Get-DPContent
    .PARAMETER DistributionPoint
        Name of distribution point (as it appears in ConfigMgr, usually FQDN) you want to remove content from.
    .PARAMETER ObjectID
        Unique ID of the content object you want to remove.

        For Applications the ID must be the CI_ID value whereas for all other content objects the ID is PackageID.

        When using this parameter you must also use ObjectType.
    .PARAMETER ObjectType
        Object type of the content object you want to remove.

        Can be one of the following values: "Package", "DriverPackage", "DeploymentPackage", "OperatingSystemImage", "OperatingSystemInstaller", "BootImage", "Application".

        When using this parameter you must also use ObjectID.
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.
        
        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.
        
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.
        
        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Get-DPContent -DistributionPoint "dp.contoso.com" | Remove-DPContent -WhatIf

        Removes all content from the distribution point "dp.contoso.com"
    .EXAMPLE
        PS C:\> Get-DPContent -DistributionPoint "dp.contoso.com" | Remove-DPContent -DistributionPoint "anotherdp.contoso.com" -WhatIf

        Removes all content found on distribution point "dp.contoso.com" from the distribution point "anotherdp.contoso.com"
    .EXAMPLE
        PS C:\> Remove-DPContent -ObjectID "17014765" -ObjectType "Application" -DistributionPoint "dp.contoso.com" -WhatIf

        Removes application with CI_ID value of "17014765" from distribution point "dp.contoso.com"
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName="InputObject")]
        [PSTypeName('PSCMContentMgmt')]
        [PSCustomObject[]]$InputObject,

        [Parameter(Mandatory, ParameterSetName="SpecifyProperties")]
        [ValidateNotNullOrEmpty()]
        [String]$ObjectID,

        [Parameter(Mandatory, ParameterSetName="SpecifyProperties")]
        [ValidateSet("Package","DriverPackage","DeploymentPackage","OperatingSystemImage","OperatingSystemInstaller","BootImage","Application")]
        [SMS_DPContentInfo]$ObjectType,

        [Parameter(ParameterSetName="InputObject")]
        [Parameter(Mandatory, ParameterSetName="SpecifyProperties")]
        [ValidateNotNullOrEmpty()]
        [String]$DistributionPoint,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }

        $TargetDP = $DistributionPoint

        if ($PSCmdlet.ParameterSetName -ne "InputObject") {
            $InputObject = [PSCustomObject]@{
                ObjectID          = $ObjectID
                ObjectType        = $ObjectType
                DistributionPoint = $TargetDP
            }
        }

        $OriginalLocation = (Get-Location).Path

        if($null -eq (Get-PSDrive -Name $SiteCode -PSProvider "CMSite" -ErrorAction "SilentlyContinue")) {
            $null = New-PSDrive -Name $SiteCode -PSProvider "CMSite" -Root $SiteServer -ErrorAction "Stop"
        }

        Set-Location ("{0}:\" -f $SiteCode) -ErrorAction "Stop"
    }
    process {
        try {
            foreach ($Object in $InputObject) {   
                switch ($true) {
                    ($LastDP -ne $Object.DistributionPoint -And -not $PSBoundParameters.ContainsKey("DistributionPoint")) {
                        $TargetDP = $Object.DistributionPoint
                    }
                    ($LastDP -ne $TargetDP) {
                        try {
                            Resolve-DP -Name $TargetDP -SiteServer $SiteServer -SiteCode $SiteCode
                        }
                        catch {
                            Write-Error -ErrorRecord $_
                            return
                        }
                        
                        $LastDP = $TargetDP
                    }
                    default {
                        $LastDP = $TargetDP
                    }
                }

                $result = @{ 
                    PSTypeName = "PSCMContentMgmtRemove"
                    ObjectID   = $Object.ObjectID
                    ObjectType = $Object.ObjectType
                    Message    = $null
                }
                
                $Command = 'Remove-CMContentDistribution -DistributionPointName "{0}" -{1} "{2}" -Force -ErrorAction "Stop"' -f $TargetDP, [SMS_DPContentInfo_CMParameters][SMS_DPContentInfo]$Object.ObjectType, $Object.ObjectID
                $ScriptBlock = [ScriptBlock]::Create($Command)
                try {
                    if ($PSCmdlet.ShouldProcess(
                        ("Would remove '{0}' ({1}) from '{2}'" -f $Object.ObjectID, $Object.ObjectType, $TargetDP),
                        "Are you sure you want to continue?",
                        ("Removing '{0}' ({1}) from '{2}'" -f $Object.ObjectID, $Object.ObjectType, $TargetDP))) {
                            Invoke-Command -ScriptBlock $ScriptBlock -ErrorAction "Stop"
                            $result["Result"] = "Success"
                    }
                    else {
                        $result["Result"] = "No change"
                    }
                }
                catch {
                    Write-Error -ErrorRecord $_
                    $result["Result"] = "Failed"
                    $result["Message"] = $_.Exception.Message
                }
                
                if (-not $WhatIfPreference) { [PSCustomObject]$result }
            }
        }
        catch {
            Set-Location $OriginalLocation 
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
        Set-Location $OriginalLocation
    }
}

function Remove-DPGroupContent {
    <#
    .SYNOPSIS
        Remove objects from a distribution point group
    .PARAMETER InputObject
        A PSObject type "PSCMContentMgmt" generated by Get-DPContent
    .PARAMETER DistributionPointGroup
        Name of distribution point group you want to remove content from.
    .PARAMETER ObjectID
        Unique ID of the content object you want to remove.

        For Applications the ID must be the CI_ID value whereas for all other content objects the ID is PackageID.

        When using this parameter you must also use ObjectType.
    .PARAMETER ObjectType
        Object type of the content object you want to remove.

        Can be one of the following values: "Package", "DriverPackage", "DeploymentPackage", "OperatingSystemImage", "OperatingSystemInstaller", "BootImage", "Application".

        When using this parameter you must also use ObjectID.
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.
        
        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.
        
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.
        
        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Get-DPGroupContent -DistributionPointGroup "Asia DPs" | Remove-DPGroupContent -WhatIf

        Removes all content from the distribution point group "Asia DPs"
    .EXAMPLE
        PS C:\> Remove-DPGroupContent -ObjectID "17014765" -ObjectType "Application" -DistributionPointGroup "Asia DPs" -WhatIf

        Removes application with CI_ID value of "17014765" from distribution point group "Asia DPs"
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName="InputObject")]
        [PSTypeName('PSCMContentMgmt')]
        [PSCustomObject[]]$InputObject,

        [Parameter(Mandatory, ParameterSetName="SpecifyProperties")]
        [ValidateNotNullOrEmpty()]
        [String]$ObjectID,

        [Parameter(Mandatory, ParameterSetName="SpecifyProperties")]
        [ValidateSet("Package","DriverPackage","DeploymentPackage","OperatingSystemImage","OperatingSystemInstaller","BootImage","Application")]
        [SMS_DPContentInfo]$ObjectType,

        [Parameter(ParameterSetName="InputObject")]
        [Parameter(Mandatory, ParameterSetName="SpecifyProperties")]
        [ValidateNotNullOrEmpty()]
        [String]$DistributionPointGroup,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }

        $TargetDPGroup = $DistributionPointGroup

        if ($PSCmdlet.ParameterSetName -ne "InputObject") {
            $InputObject = [PSCustomObject]@{
                ObjectID               = $ObjectID
                ObjectType             = $ObjectType
                DistributionPointGroup = $TargetDPGroup
            }
        }
        
        $OriginalLocation = (Get-Location).Path

        if($null -eq (Get-PSDrive -Name $SiteCode -PSProvider "CMSite" -ErrorAction "SilentlyContinue")) {
            $null = New-PSDrive -Name $SiteCode -PSProvider "CMSite" -Root $SiteServer -ErrorAction "Stop"
        }

        Set-Location ("{0}:\" -f $SiteCode) -ErrorAction "Stop"
    }
    process {
        try {
            foreach ($Object in $InputObject) {
                switch ($true) {
                    ($LastDPGroup -ne $Object.DistributionPointGroup -And -not $PSBoundParameters.ContainsKey("DistributionPointGroup")) {
                        $TargetDPGroup = $Object.DistributionPointGroup
                    }
                    ($LastDPGroup -ne $TargetDPGroup) {
                        try {
                            Resolve-DPGroup -Name $TargetDPGroup -SiteServer $SiteServer -SiteCode $SiteCode
                        }
                        catch {
                            Write-Error -ErrorRecord $_
                            return
                        }
                        
                        $LastDPGroup = $TargetDPGroup
                    }
                    default {
                        $LastDPGroup = $TargetDPGroup
                    }
                }
                
                $result = @{ 
                    PSTypeName             = "PSCMContentMgmtRemove"
                    ObjectID               = $Object.ObjectID
                    ObjectType             = $Object.ObjectType
                    DistributionPointGroup = $TargetDPGroup
                    Message                = $null
                }
                
                $Command = 'Remove-CMContentDistribution -DistributionPointGroupName "{0}" -{1} "{2}" -Force -ErrorAction "Stop"' -f $TargetDPGroup, [SMS_DPContentInfo_CMParameters][SMS_DPContentInfo]$Object.ObjectType, $Object.ObjectID
                $ScriptBlock = [ScriptBlock]::Create($Command)
                try {
                    if ($PSCmdlet.ShouldProcess(
                        ("Would remove '{0}' ({1}) from '{2}'" -f $Object.ObjectID, $Object.ObjectType, $TargetDPGroup),
                        "Are you sure you want to continue?",
                        ("Removing '{0}' ({1}) from '{2}'" -f $Object.ObjectID, $Object.ObjectType, $TargetDPGroup))) {
                            Invoke-Command -ScriptBlock $ScriptBlock -ErrorAction "Stop"
                            $result["Result"] = "Success"
                    }
                    else {
                        $result["Result"] = "No change"
                    }
                }
                catch {
                    Write-Error -ErrorRecord $_
                    $result["Result"] = "Failed"
                    $result["Message"] = $_.Exception.Message
                }
                
                if (-not $WhatIfPreference) { [PSCustomObject]$result }
            }
        }
        catch {
            Set-Location $OriginalLocation 
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
        Set-Location $OriginalLocation
    }
}

function Set-DPAllowPrestagedContent {
    <#
    .SYNOPSIS
        Configure the allow prestage content setting for a distribution point
    .PARAMETER DistributionPoint
        Name of distribution point (as it appears in ConfigMgr, usually FQDN) you want to change the setting on.
    .PARAMETER State
        A boolean value, $true configures the distribution point to allow prestage contet whereas $false removes the config.

        This is the equivilant of checking the box in the distribution point's properties for "Enables this distribution point for prestaged content". Checked = $true, unchecked = $false.
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.
        
        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.
        
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.
        
        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Set-DPAllowPrestageContent -DistributionPoint "dp1.contoso.com" -State $true -WhatIf

        Enables "dp1.contoso.com" to allow prestaged content.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]$DistributionPoint,

        [Parameter()]
        [Bool]$State = $true,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }

        try {
            Resolve-DP -Name $DistributionPoint -SiteServer $SiteServer -SiteCode $SiteCode
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }

        $Action = switch ($State) {
            $true { "enable" }
            $false { "disable" }
        }

        $OriginalLocation = (Get-Location).Path

        if($null -eq (Get-PSDrive -Name $SiteCode -PSProvider "CMSite" -ErrorAction "SilentlyContinue")) {
            $null = New-PSDrive -Name $SiteCode -PSProvider "CMSite" -Root $SiteServer -ErrorAction "Stop"
        }

        Set-Location ("{0}:\" -f $SiteCode) -ErrorAction "Stop"
    }
    process {
        try {
            $result = @{
                PSTypeName        = "PSCMContentMgmtPrestageSetting"
                DistributionPoint = $DistributionPoint
                Message           = $null
            }
            try {
                if ($PSCmdlet.ShouldProcess(
                    ("Would {0} allowing prestage content on '{1}'" -f $Action, $DistributionPoint),
                    "Are you sure you want to continue?",
                    ("Warning: Changing allow prestage setting to {0}d for '{1}'" -f $Action, $DistributionPoint))) {
                        Set-CMDistributionPoint -SiteSystemServerName $DistributionPoint -AllowPreStaging $State
                        $result["Result"] = "Success"
                }
                else {
                    $result["Result"] = "No change"
                }
            }
            catch {
                Write-Error -ErrorRecord $_
                $result["Result"] = "Failed"
                $result["Message"] = $_.Exception.Message
            }
            
            if (-not $WhatIfPreference) { [PSCustomObject]$result }
        }
        catch {
            Set-Location $OriginalLocation 
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
        Set-Location $OriginalLocation
    }
}

function Start-DPContentDistribution {
    <#
    .SYNOPSIS
        Distributes objects to a given distribution point. The function can accept input object from Get-DPContent, by manually specifying -ObjectID and -ObjectType or by using -Folder where it will distribute all objects for .pkgx files found in said folder.
    .PARAMETER InputObject
        A PSObject type "PSCMContentMgmt" generated by Get-DPContent
    .PARAMETER DistributionPoint
        Name of distribution point (as it appears in ConfigMgr, usually FQDN) you want to distribute objects to.
    .PARAMETER ObjectID
        Unique ID of the content object you want to distribute.

        For Applications the ID must be the CI_ID value whereas for all other content objects the ID is PackageID.

        When using this parameter you must also use ObjectType.
    .PARAMETER ObjectType
        Object type of the content object you want to distribute.

        Can be one of the following values: "Package", "DriverPackage", "DeploymentPackage", "OperatingSystemImage", "OperatingSystemInstaller", "BootImage", "Application".

        When using this parameter you must also use ObjectID.
    .PARAMETER Folder
        For all .pkgx files in this folder that use the following naming convention "<ObjectType>_<ObjectID>.pkgx", distribute the <ObjectID> of type <ObjectType> to -DistributionPoint.

        This can be useful if you have a folder filled with .pkgx files, generated by Export-DPContent, and want to distribute those objects to a distribution point.
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.
        
        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.
        
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.
        
        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Compare-DPContent -Source "dp1.contoso.com" -Target "dp2.contoso.com" | Start-DPContentDistribution -DistributionPoint "dp2.contoso.com" -WhatIf

        Compares the missing content objects on "dp2.contoso.com" compared to "dp1.contoso.com", and distributes them to "dp2.contoso.com".
    .EXAMPLE
        PS C:\> Start-DPContentDistribution -Folder "E:\exported" -DistributionPoint "dp2.contoso.com" -WhatIf

        For all .pkgx files in folder "E:\exported" that use the following naming convention "<ObjectType>_<ObjectID>.pkgx", distributes them to "dp2.contoso.com".
    .EXAMPLE
        PS C:\> Start-DPContentDistribution -ObjectID ACC00007 -ObjectType Package -DistributionPoint "dp2.contoso.com" -WhatIf
        
        Nothing more than a wrapper for Start-CMContentDistribution. Distributes package ACC00007 to "dp2.contoso.com".
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName="InputObject")]
        [PSTypeName('PSCMContentMgmt')]
        [PSCustomObject]$InputObject,

        [Parameter(Mandatory, ParameterSetName="Properties")]
        [ValidateNotNullOrEmpty()]
        [String]$ObjectID,

        [Parameter(Mandatory, ParameterSetName="Properties")]
        [ValidateSet("Package","DriverPackage","DeploymentPackage","OperatingSystemImage","OperatingSystemInstaller","BootImage","Application")]
        [SMS_DPContentInfo]$ObjectType,

        [Parameter(Mandatory, ParameterSetName="Folder")]
        [ValidateScript({
            if (!([System.IO.Directory]::Exists($_))) {
                throw "Invalid path or access denied"
            } elseif (!($_ | Test-Path -PathType Container)) {
                throw "Value must be a directory, not a file"
            } else {
                return $true
            }
        })]
        [String]$Folder,

        [Parameter(ParameterSetName="InputObject")]
        [Parameter(Mandatory, ParameterSetName="Properties")]
        [Parameter(Mandatory, ParameterSetName="Folder")]
        [ValidateNotNullOrEmpty()]
        [String]$DistributionPoint,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }

        $TargetDP = $DistributionPoint

        if ($PSCmdlet.ParameterSetName -ne "InputObject") {
            $InputObject = [PSCustomObject]@{
                ObjectID          = $ObjectID
                ObjectType        = $ObjectType
                Distributionpoint = $TargetDP
            }
        }

        if ($PSCmdlet.ParameterSetName -eq "Folder") {
            $Files = Get-ChildItem -Path $Folder -Filter "*.pkgx"

            try {
                Resolve-DP -Name $TargetDP -SiteServer $SiteServer -SiteCode $SiteCode
            }
            catch {
                $PSCmdlet.ThrowTerminatingError($_)
            }
        }

        $OriginalLocation = (Get-Location).Path

        if ($null -eq (Get-PSDrive -Name $SiteCode -PSProvider "CMSite" -ErrorAction "SilentlyContinue")) {
            $null = New-PSDrive -Name $SiteCode -PSProvider "CMSite" -Root $SiteServer -ErrorAction "Stop"
        }

        Set-Location ("{0}:\" -f $SiteCode) -ErrorAction "Stop"
    }
    process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                "Folder" {
                    foreach ($File in $Files) {
                        if ($File.Name -match "^(?<ObjectType>0|3|5|257|258|259|512)_(?<ObjectID>[A-Za-z0-9]+)\.pkgx$") {
                            $InputObject = [PSCustomObject]@{
                                ObjectID   = $Matches.ObjectID
                                ObjectType = $Matches.ObjectType
                            }
        
                            $result = @{
                                PSTypeName = "PSCMContentMgmtDistribute" 
                                ObjectID   = $InputObject.ObjectID
                                ObjectType = [SMS_DPContentInfo]$InputObject.ObjectType
                                Message    = $null
                            }

                            $Command = 'Start-CMContentDistribution -{0} "{1}" -DistributionPointName "{2}" -ErrorAction "Stop"' -f [SMS_DPContentInfo_CMParameters][SMS_DPContentInfo]$InputObject.ObjectType, $InputObject.ObjectID, $TargetDP
                            $ScriptBlock = [ScriptBlock]::Create($Command)
                            try {
                                if ($PSCmdlet.ShouldProcess(
                                    ("Would distribute '{0}' ({1}) to '{2}'" -f $InputObject.ObjectID, [SMS_DPContentInfo]$InputObject.ObjectType, $TargetDP),
                                    "Are you sure you want to continue?",
                                    ("Distributing '{0}' ({1}) to '{2}'" -f $InputObject.ObjectID, [SMS_DPContentInfo]$InputObject.ObjectType, $TargetDP))) {
                                        Invoke-Command -ScriptBlock $ScriptBlock -ErrorAction "Stop"
                                        $result["Result"] = "Success"
                                }
                                else {
                                    $result["Result"] = "No change"
                                }
                            }
                            catch {
                                Write-Error -ErrorRecord $_
                                $result["Result"] = "Failed"
                                $result["Message"] = $_.Exception.Message
                            }
                            
                            if (-not $WhatIfPreference) { [PSCustomObject]$result }
                        }
                        else {
                            Write-Warning ("Skipping '{0}'" -f $File.Name)
                        }
                    }
                }
                default {
                    foreach ($Object in $InputObject) {
                        switch ($true) {
                            ($LastDP -ne $Object.DistributionPoint -And -not $PSBoundParameters.ContainsKey("DistributionPoint")) {
                                $TargetDP = $Object.DistributionPoint
                            }
                            ($LastDP -ne $TargetDP) {
                                try {
                                    Resolve-DP -Name $TargetDP -SiteServer $SiteServer -SiteCode $SiteCode
                                }
                                catch {
                                    Write-Error -ErrorRecord $_
                                    return
                                }
                                
                                $LastDP = $TargetDP
                            }
                            default {
                                $LastDP = $TargetDP
                            }
                        }

                        $result = @{
                            PSTypeName = "PSCMContentMgmtDistribute" 
                            ObjectID   = $Object.ObjectID
                            ObjectType = $Object.ObjectType
                            Message    = $null
                        }
        
                        $Command = 'Start-CMContentDistribution -{0} "{1}" -DistributionPointName "{2}" -ErrorAction "Stop"' -f [SMS_DPContentInfo_CMParameters][SMS_DPContentInfo]$Object.ObjectType, $Object.ObjectID, $TargetDP
                        $ScriptBlock = [ScriptBlock]::Create($Command)
                        try {
                            if ($PSCmdlet.ShouldProcess(
                                ("Would distribute '{0}' ({1}) to '{2}'" -f $Object.ObjectID, $Object.ObjectType, $TargetDP),
                                "Are you sure you want to continue?",
                                ("Distributing '{0}' ({1}) to '{2}'" -f $Object.ObjectID, $Object.ObjectType, $TargetDP))) {
                                    Invoke-Command -ScriptBlock $ScriptBlock -ErrorAction "Stop"
                                    $result["Result"] = "Success"
                            }
                            else {
                                $result["Result"] = "No change"
                            }
                        }
                        catch {
                            Write-Error -ErrorRecord $_
                            $result["Result"] = "Failed"
                            $result["Message"] = $_.Exception.Message
                        }
                        
                        if (-not $WhatIfPreference) { [PSCustomObject]$result }
                    }
                }
            }
        }
        catch {
            Set-Location $OriginalLocation 
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
        Set-Location $OriginalLocation
    }
}

function Start-DPGroupContentDistribution {
    <#
    .SYNOPSIS
        Distributes objects to a given distribution point group.
        
        The function can accept input object from Get-DPContent, by manually specifying -ObjectID and -ObjectType or by using -Folder where it will distribute all objects for .pkgx files found in said folder.
    .PARAMETER InputObject
        A PSObject type "PSCMContentMgmt" generated by Get-DPContent
    .PARAMETER DistributionPointGroup
        Name of distribution point group you want to distribute objects to.
    .PARAMETER ObjectID
        Unique ID of the content object you want to distribute.

        For Applications the ID must be the CI_ID value whereas for all other content objects the ID is PackageID.

        When using this parameter you must also use ObjectType.
    .PARAMETER ObjectType
        Object type of the content object you want to distribute.

        Can be one of the following values: "Package", "DriverPackage", "DeploymentPackage", "OperatingSystemImage", "OperatingSystemInstaller", "BootImage", "Application".

        When using this parameter you must also use ObjectID.
    .PARAMETER Folder
        For all .pkgx files in this folder that use the following naming convention "<ObjectType>_<ObjectID>.pkgx", distribute the <ObjectID> of type <ObjectType> to -DistributionPoint.

        This can be useful if you have a folder filled with .pkgx files, generated by Export-DPContent, and want to distribute those objects to a distribution point.
    .PARAMETER SiteServer
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteServer variable which is the default value for this parameter.
        
        Specify this to query an alternative server, or if the module import process was unable to auto-detect and set $CMSiteServer.
    .PARAMETER SiteCode
        Site code of which the server specified by -SiteServer belongs to.
        
        It is not usually necessary to specify this parameter as importing the PSCMContentMgr module sets the $CMSiteCode variable which is the default value for this parameter.
        
        Specify this to query an alternative site, or if the module import process was unable to auto-detect and set $CMSiteCode.
    .EXAMPLE
        PS C:\> Compare-DPGroupContent -Source "London DPs" -Target "Mancester DPs" | Start-DPGroupContentDistribution -DistributionPointGroup "Mancester DPs" -WhatIf

        Compares the missing content objects in group Manchester DPs compared to "London DPs", and distributes them to distribution point group Manchester DPs.
    .EXAMPLE
        PS C:\> Start-DPGroupContentDistribution -Folder "E:\exported" -DistributionPointGroup "London DPs" -WhatIf

        For all .pkgx files in folder "E:\exported" that use the following naming convention "<ObjectType>_<ObjectID>.pkgx", distributes them to distribution point group "London DPs".
    .EXAMPLE
        PS C:\> Start-DPGroupContentDistribution -ObjectID ACC00007 -ObjectType Package -DistributionPointGroup "London DPs" -WhatIf
        
        Nothing more than a wrapper for Start-CMContentDistribution. Distributes package ACC00007 to distribution point group "London DPs".
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName="InputObject")]
        [PSTypeName('PSCMContentMgmt')]
        [PSCustomObject]$InputObject,

        [Parameter(Mandatory, ParameterSetName="Properties")]
        [ValidateNotNullOrEmpty()]
        [String]$ObjectID,

        [Parameter(Mandatory, ParameterSetName="Properties")]
        [ValidateSet("Package","DriverPackage","DeploymentPackage","OperatingSystemImage","OperatingSystemInstaller","BootImage","Application")]
        [SMS_DPContentInfo]$ObjectType,

        [Parameter(Mandatory, ParameterSetName="Folder")]
        [ValidateScript({
            if (!([System.IO.Directory]::Exists($_))) {
                throw "Invalid path or access denied"
            } elseif (!($_ | Test-Path -PathType Container)) {
                throw "Value must be a directory, not a file"
            } else {
                return $true
            }
        })]
        [String]$Folder,

        [Parameter(ParameterSetName="InputObject")]
        [Parameter(Mandatory, ParameterSetName="Properties")]
        [Parameter(Mandatory, ParameterSetName="Folder")]
        [ValidateNotNullOrEmpty()]
        [String]$DistributionPointGroup,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteServer = $CMSiteServer,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]$SiteCode = $CMSiteCode
    )
    begin {
        switch ($null) {
            $SiteCode {
                Write-Error -Message "Please supply a site code using the -SiteCode parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
            $SiteServer {
                Write-Error -Message "Please supply a site server FQDN address using the -SiteServer parameter" -Category "InvalidArgument" -ErrorAction "Stop"
            }
        }

        $TargetDPGroup = $DistributionPointGroup

        if ($PSCmdlet.ParameterSetName -ne "InputObject") {
            $InputObject = [PSCustomObject]@{
                ObjectID               = $ObjectID
                ObjectType             = $ObjectType
                DistributionPointGroup = $TargetDPGroup
            }
        }

        if ($PSCmdlet.ParameterSetName -eq "Folder") {
            $Files = Get-ChildItem -Path $Folder -Filter "*.pkgx"

            try {
                Resolve-DPGroup -Name $TargetDPGroup -SiteServer $SiteServer -SiteCode $SiteCode
            }
            catch {
                $PSCmdlet.ThrowTerminatingError($_)
            }
        }

        $OriginalLocation = (Get-Location).Path

        if ($null -eq (Get-PSDrive -Name $SiteCode -PSProvider "CMSite" -ErrorAction "SilentlyContinue")) {
            $null = New-PSDrive -Name $SiteCode -PSProvider "CMSite" -Root $SiteServer -ErrorAction "Stop"
        }

        Set-Location ("{0}:\" -f $SiteCode) -ErrorAction "Stop"
    }
    process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                "Folder" {
                    foreach ($File in $Files) {
                        if ($File.Name -match "^(?<ObjectType>0|3|5|257|258|259|512)_(?<ObjectID>[A-Za-z0-9]+)\.pkgx$") {
                            $InputObject = [PSCustomObject]@{
                                ObjectID   = $Matches.ObjectID
                                ObjectType = $Matches.ObjectType
                            }
        
                            $result = @{
                                PSTypeName = "PSCMContentMgmtDistribute" 
                                ObjectID   = $InputObject.ObjectID
                                ObjectType = [SMS_DPContentInfo]$InputObject.ObjectType
                                Message    = $null
                            }

                            $Command = 'Start-CMContentDistribution -{0} "{1}" -DistributionPointGroupName "{2}" -ErrorAction "Stop"' -f [SMS_DPContentInfo_CMParameters][SMS_DPContentInfo]$InputObject.ObjectType, $InputObject.ObjectID, $TargetDPGroup
                            $ScriptBlock = [ScriptBlock]::Create($Command)
                            try {
                                if ($PSCmdlet.ShouldProcess(
                                    ("Would distribute '{0}' ({1}) to '{2}'" -f $InputObject.ObjectID, [SMS_DPContentInfo]$InputObject.ObjectType, $TargetDPGroup),
                                    "Are you sure you want to continue?",
                                    ("Distributing '{0}' ({1}) to '{2}'" -f $InputObject.ObjectID, [SMS_DPContentInfo]$InputObject.ObjectType, $TargetDPGroup))) {
                                        Invoke-Command -ScriptBlock $ScriptBlock -ErrorAction "Stop"
                                        $result["Result"] = "Success"
                                }
                                else {
                                    $result["Result"] = "No change"
                                }
                            }
                            catch {
                                Write-Error -ErrorRecord $_
                                $result["Result"] = "Failed"
                                $result["Message"] = $_.Exception.Message
                            }
                            
                            if (-not $WhatIfPreference) { [PSCustomObject]$result }
                        }
                        else {
                            Write-Warning ("Skipping '{0}'" -f $File.Name)
                        }
                    }
                }
                default {
                    foreach ($Object in $InputObject) {
                        switch ($true) {
                            ($LastDPGroup -ne $Object.DistributionPointGroup -And -not $PSBoundParameters.ContainsKey("DistributionPointGroup")) {
                                $TargetDPGroup = $Object.DistributionPointGroup
                            }
                            ($LastDPGroup -ne $TargetDPGroup) {
                                try {
                                    Resolve-DPGroup -Name $TargetDPGroup -SiteServer $SiteServer -SiteCode $SiteCode
                                }
                                catch {
                                    Write-Error -ErrorRecord $_
                                    return
                                }
                                
                                $LastDPGroup = $TargetDPGroup
                            }
                            default {
                                $LastDPGroup = $TargetDPGroup
                            }
                        }

                        $result = @{
                            PSTypeName = "PSCMContentMgmtDistribute" 
                            ObjectID   = $Object.ObjectID
                            ObjectType = $Object.ObjectType
                            Message    = $null
                        }
        
                        $Command = 'Start-CMContentDistribution -{0} "{1}" -DistributionPointGroupName "{2}" -ErrorAction "Stop"' -f [SMS_DPContentInfo_CMParameters][SMS_DPContentInfo]$Object.ObjectType, $Object.ObjectID, $TargetDPGroup
                        $ScriptBlock = [ScriptBlock]::Create($Command)
                        try {
                            if ($PSCmdlet.ShouldProcess(
                                ("Would distribute '{0}' ({1}) to '{2}'" -f $Object.ObjectID, $Object.ObjectType, $TargetDPGroup),
                                "Are you sure you want to continue?",
                                ("Distributing '{0}' ({1}) to '{2}'" -f $Object.ObjectID, $Object.ObjectType, $TargetDPGroup))) {
                                    Invoke-Command -ScriptBlock $ScriptBlock -ErrorAction "Stop"
                                    $result["Result"] = "Success"
                            }
                            else {
                                $result["Result"] = "No change"
                            }
                        }
                        catch {
                            Write-Error -ErrorRecord $_
                            $result["Result"] = "Failed"
                            $result["Message"] = $_.Exception.Message
                        }
                        
                        if (-not $WhatIfPreference) { [PSCustomObject]$result }
                    }
                }
            }
        }
        catch {
            Set-Location $OriginalLocation 
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
        Set-Location $OriginalLocation
    }
}
#endregion