Public/Move-PdqDeployItem.ps1

<#
.SYNOPSIS
Moves whatever you select into a Folder by reading the contents of the clipboard.
 
.DESCRIPTION
Created for https://help.pdq.com/hc/en-us/community/posts/360074687911
 
Reads the contents of your clipboard to get data about the Folders, Packages, and/or Target Lists you selected,
then the Folder you want to move them into, then updates the Path and FolderId of the items in your database.
 
.INPUTS
None.
 
.OUTPUTS
None.
 
.EXAMPLE
Move-PdqDeployItem
Displays a couple of prompts, then updates your database.
#>

function Move-PdqDeployItem {

    [CmdletBinding()]
    param (
    )

    Write-Host ''
    Write-Host 'Copy the Folders, Packages, and/or Target Lists you would like to move.'
    Wait-PdqClipboardData -Format 'Package'
    $ItemsToMove = (Get-PdqClipboardData -Format 'Package').'AdminArsenal.Export'

    $FoldersToMove = $ItemsToMove.Folder | Select-Object Id, Name
    $TargetListsToMove = $ItemsToMove.TargetList | Select-Object Id, Name
    $PackagesToMove = $ItemsToMove.Package | Select-Object Path, Name

    # When there's only 1 of an item, its object is not an array, so you have to force it to be an array to get
    # an accurate count.
    $ItemCount = @($FoldersToMove).Count + @($TargetListsToMove).Count + @($PackagesToMove).Count
    if ( $ItemCount -eq 0 ) {

        throw 'No Folders, Packages, or Target Lists were found on the clipboard.'

    }

    Write-Host "Copy the Folder you would like to move $ItemCount item(s) into."
    Wait-PdqClipboardData -Format 'DeployFolder'

    $DestinationFolder = (Get-PdqClipboardData -Format 'DeployFolder').'AdminArsenal.Export'.Folder
    if ( $DestinationFolder.Count -gt 1 ) {

        throw 'You cannot select more than 1 folder as the destination.'

    }
    [Int32]$DestinationFolderId = $DestinationFolder.Id.value
    $DestinationFolderPath = $DestinationFolder.Path
    if ( -not $DestinationFolderId ) {

        throw 'Unable to retrieve the folder id from the clipboard.'

    }
    if ( -not $DestinationFolderPath ) {

        throw 'Unable to retrieve the folder path from the clipboard.'

    }
    if ( $DestinationFolderId -in $FoldersToMove.Id.value ) {

        throw 'Cannot move a Folder into itself.'

    }

    try {

        $CloseConnection = Open-PdqSqlConnection -Product 'Deploy'

        # Make sure the destination folder is not a child of any of the source folders.
        # https://gitlab.com/ColbyBouma/pdqstuff/-/issues/65
        if ( $FoldersToMove ) {

            # Build a hashtable so it's easier to work with the data.
            $AllFolders = @{}
            Invoke-PdqSqlQuery -Query 'SELECT * FROM Folders;' -Product 'Deploy' -Stream | ForEach-Object {

                # SimplySql returns an Int64 for FolderId, but hashtable keys have to be Int32.
                $AllFolders[[Int32]$_.FolderId] = $_

            }

            # Build an array that contains the ParentId for all of the destination folder's parents.
            # It's OK if the first $CurrentParentId is null, that just means it doesn't have a parent folder.
            $ParentIdTree = @()
            [Int32]$CurrentParentId = $AllFolders[$DestinationFolderId].ParentId
            while ( $CurrentParentId ) {

                $ParentIdTree += $CurrentParentId
                $CurrentParentId = $AllFolders[$CurrentParentId].ParentId

            }
            
            # It sucks to loop through this twice, but if this error is going to be thrown, I want it to happen
            # before *any* data is altered.
            foreach ( $Folder in $FoldersToMove ) {

                if ( $Folder.Id.value -in $ParentIdTree ) {

                    throw 'Cannot move a Folder into its own child.'

                }

            }

        }

        foreach ( $Folder in $FoldersToMove ) {

            $NewPath = $DestinationFolderPath, $Folder.Name -join '\'
            $FolderId = $Folder.Id.value
            $Query = "UPDATE Folders SET (ParentId, Path) = ($DestinationFolderId, '$NewPath') WHERE FolderId = $FolderId;"
            Write-Verbose $Query
            $null = Invoke-SqlUpdate -Query $Query -ConnectionName 'Deploy'

        }

        foreach ( $TargetList in $TargetListsToMove ) {

            $NewPath = $DestinationFolderPath, $TargetList.Name -join '\'
            $TargetListId = $TargetList.Id.value
            $Query = "UPDATE TargetLists SET (FolderId, Path) = ($DestinationFolderId, '$NewPath') WHERE TargetListId = $TargetListId;"
            Write-Verbose $Query
            $null = Invoke-SqlUpdate -Query $Query -ConnectionName 'Deploy'

        }

        foreach ( $Package in $PackagesToMove ) {

            $PackagePath = $Package.Path
            $PackageName = $Package.Name
            
            $PackageIdQuery = "SELECT PackageId FROM Packages WHERE Path = '$PackagePath';"
            $PackageId = (Invoke-SqlQuery -Query $PackageIdQuery -ConnectionName 'Deploy').PackageId
            switch ( $PackageId.Count ) {

                0 {

                    throw "Unable to find a PackageId for '$PackagePath'."

                }

                { $_ -gt 1 } {

                    throw "Multiple IDs were returned for '$PackagePath'."

                }

            }

            $NewPath = $DestinationFolderPath, $PackageName -join '\'
            $Query = "UPDATE Packages SET (FolderId, Path) = ($DestinationFolderId, '$NewPath') WHERE PackageId = $PackageId;"
            Write-Verbose $Query
            $null = Invoke-SqlUpdate -Query $Query -ConnectionName 'Deploy'

        }

        Write-Host ''
        Write-Host 'Success! Please refresh the PDQ Deploy console.'

    } finally {

        Close-PdqSqlConnection -Product 'Deploy' -CloseConnection $CloseConnection

    }

}