Public/Invoke-ATMDFileDeployment.ps1

function Invoke-ATMDFileDeployment {
    <#
    .SYNOPSIS
        Запускает копирование папок и файлов.
    .DESCRIPTION
        Запускает копирование файлов и папок на основании представленных параметрв в виде объектов / хеш таблиц (см. пример).
    .PARAMETER FileDeployment
        Объект с описание файлов (см. пример).
    .PARAMETER PassThru
        Используйте параметр -PassThru, чтобы получить список обработаных файлов и код завершения операций.
    .EXAMPLE
        $fileDeployment = @{
            foldersToDelete =@('E:\tmp\#Daily\qqq')
            foldersToClear = @(
                @{
                    path = 'E:\tmp\#Daily\Tools2\Tools'
                    excludeList = @('#IDF', 'SysInts')
                }
            )
            filesToDelete =@('E:\tmp\#Daily\new.txt')
            filesToCopy =@(
                @{
                    source = '\\podsrv-rdsfs1.corp.atmd.ru\SysUsrData\CSPRegCerts\AMD\romanova\2019-12-16 14-25-06 ООО АМД'
                    destination = 'E:\tmp\#Daily\new.reg'
                },
                @{
                    source = '\\podsrv-rdsfs1.corp.atmd.ru\SysUsrData\CSPRegCerts\AMD\romanova\c1d136ec-d665-4db1-b0ca-ed836a270293'
                    destination = 'E:\tmp\#Daily\new2.reg'
                    forceReplace = 'True'
                }
            )
            filesToUpdate =@(
                @{
                    source = '\\srv-fs1.corp.atmd.ru\Deploy\Tools\LAPSx64.msi'
                    destination = 'E:\tmp\#Daily\LAPSx64.msi'
                },
                @{
                    source = '\\srv-fs1.corp.atmd.ru\Deploy\Tools\PoSh\LogonScript\Atmd-LogonScript.ps1'
                    destination = 'E:\tmp\#Daily\Atmd-LogonScript.ps1'
                }
            )
        }
 
        Invoke-ATMDFileDeployment -FileDeployment $FileDeployment -PassThru -Verbose
    .INPUTS
        Inputs (if any)
    .OUTPUTS
        Функция возвращает объект, содержащий перечень удалнных, обнавленных и созданных файлов и папок, если указан параметр -PassThru
    .NOTES
        Цель написания этой функции - возможность быстро копировать или удалять файлы на основании описания, которое будте храниться в JSON-файле.
        Фактически, данная функция просто собирает вместе несколько командлетов Powershell, и ряд стандартный проверок, вроде Test-Path и т.п.
 
        Отдельно стоит выделить процесс "обновления файлов" - копирование с заменой файлов, основываясь на дате изменения, а для скриптов Powershell - основываясь на версии в PSInfo
 
        Возвращаемый результат и коды ошибок описаны следующим образом:
 
        $RESULT_REMOVE_FORDER_EXCEPTION = 128 #Ошибка при удалении каталога.
        $RESULT_REMOVE_FILE_EXCEPTION = 64 #Ошибка при удалении файла.
        $RESULT_COPY_FILE_EXCEPTION = 32 #Ошибка при копировании файла.
        $RESULT_COPY_FOLDER_EXCEPTION = 16 #Ошибка при копировании папки целиком.
        $RESULT_CLEAR_FORDER_EXCEPTION = 8 #Ошибка при очистки каталога.
        $RESULT_UPDATE_FILE_EXCEPTION = 4 #Ошибка при обновлении файла.
        $RESULT_NO_ERROR = 0 #Ошибок не.
 
        $Result = [PSCustomObject]@{
            DeletedFolders = @()
            DeletedFiles = @()
            CleanedFolders = @()
            CopiedFolders = @()
            CopiedFiles = @()
            ProblemItems = @()
            ExitCode = $RESULT_NO_ERROR
        }
 
        В массив $Result.ProblemItems попадают элементы, при работе с которыми возникли исключения. Остальные названия, думаю, понятны.
        Коды ошибок объединяются бинарными операциями. То есть, значение 160 указывает, что произошли ошибки при удалении папки и при копировании файла:
        ($RESULT_REMOVE_FORDER_EXCEPTION -bor $RESULT_COPY_FILE_EXCEPTION) рабно 160.
    #>

    [CmdletBinding()]
    [OutputType([System.Object])]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [System.Object]
        $FileDeployment,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 1)]
        [switch]
        $PassThru = $false
    )

    begin {
        # Для удобства чтения - коды ошибок в виде переменных с мнемоническими названиями.
        $RESULT_REMOVE_FORDER_EXCEPTION = 128  #Ошибка при удалении каталога.
        $RESULT_REMOVE_FILE_EXCEPTION = 64  #Ошибка при удалении файла.
        $RESULT_COPY_FILE_EXCEPTION = 32  #Ошибка при копировании файла.
        $RESULT_COPY_FOLDER_EXCEPTION = 16  #Ошибка при копировании папки целиком.
        $RESULT_CLEAR_FORDER_EXCEPTION = 8  #Ошибка при очистки каталога.
        $RESULT_UPDATE_FILE_EXCEPTION = 4  #Ошибка при обновлении файла.
        $RESULT_NO_ERROR = 0  #Ошибок не.

        $Result = [PSCustomObject]@{
            DeletedFolders = @()
            DeletedFiles   = @()
            CleanedFolders = @()
            CopiedFolders  = @()
            CopiedFiles    = @()
            ProblemItems   = @()
            ExitCode       = $RESULT_NO_ERROR
        }
    }

    process {
        try {
            #region Удаляем каталоги.
            foreach ($Folder in $FileDeployment.foldersToDelete) {
                try {
                    if (Test-Path -Path $Folder) {
                        Write-Verbose -Message "<Invoke-ATMDFileDeployment> Удаляем каталог $Folder"
                        Remove-Item -Path $Folder -Recurse -Force
                        $Result.DeletedFolders += $Folder
                    }
                }
                catch {
                    Write-Error -Message "При удалении каталога $Folder произшла ошибка $($PSItem.Exception.Message)"
                    $Result.ProblemItems += $Folder
                    $Result.ExitCode = $Result.ExitCode -bor $RESULT_REMOVE_FORDER_EXCEPTION
                }
            }
            #endregion Удаляем каталоги.

            #region Очищаем каталоги.
            foreach ($Folder in $FileDeployment.foldersToClear) {
                try {
                    if (Test-Path -Path $Folder.path) {
                        Write-Verbose -Message "<Invoke-ATMDFileDeployment> Очищаем каталог $($Folder.path)"
                        Get-ChildItem -Path $Folder.path -Exclude $Folder.excludeList | Remove-Item -Recurse -Force -Confirm:$false -ErrorAction Stop
                        $Result.CleanedFolders += $Folder.path
                    }
                }
                catch {
                    Write-Error -Message "При очистки каталога $($Folder.path) произшла ошибка $($PSItem.Exception.Message)"
                    $Result.ProblemItems += $Folder.path
                    $Result.ExitCode = $Result.ExitCode -bor $RESULT_CLEAR_FORDER_EXCEPTION
                }
            }
            #endregion Очищаем каталоги.

            #region Удаляем файлы.
            foreach ($File in $FileDeployment.filesToDelete) {
                try {
                    if (Test-Path -Path $File) {
                        Write-Verbose -Message "<Invoke-ATMDFileDeployment> Удаляем файл $File"
                        Remove-Item -Path $File -Force -ErrorAction Stop
                        $Result.DeletedFiles += $File
                    }
                }
                catch {
                    Write-Error -Message "При удалении файла $File произшла ошибка $($PSItem.Exception.Message)"
                    $Result.ProblemItems += $File
                    $Result.ExitCode = $Result.ExitCode -bor $RESULT_REMOVE_FILE_EXCEPTION
                }
            }
            #endregion Копируем папки.

            #region Копируем папки.
            foreach ($FolderPair in $FileDeployment.folderToCopy) {
                try {
                    if (Test-Path -Path $Folder.source) {
                        if (-not(Test-Path -Path $Folder.destination)) {
                            New-Item -Path $Folder.destination -ItemType Directory
                        }
                        Write-Verbose -Message "<Invoke-ATMDFileDeployment> Копируем каталог$($Folder.source). Место назначения - $($Folder.destination)"
                        Copy-Item -Path $Folder.source -Destination $Folder.destination -Recurse -ErrorAction Stop
                        $Result.CopiedFolders += $Folder.source
                    }
                    else {
                        Write-Warning -Message "Каталог $($FolderPair.source) не найден."
                    }
                }
                catch {
                    Write-Error -Message "При копировании каталога $($Folder.source) произшла ошибка $($PSItem.Exception.Message)"
                    $Result.ProblemItems += $Folder.source
                    $Result.ExitCode = $Result.ExitCode -bor $RESULT_COPY_FOLDER_EXCEPTION
                }
            }
            #endregion Копируем папки.

            #region Копируем файлы
            foreach ($FilePair in $FileDeployment.filesToCopy) {
                try {
                    # Проверяем, существует ли папка для целевого файла.
                    $DestinationFilePath = Split-Path -Path $FilePair.destination -Parent
                    if (-not(Test-Path -Path $DestinationFilePath)) {
                        New-Item -ItemType Directory -Path $DestinationFilePath
                    }
                    # Копируем файлы из списка
                    Write-Verbose -Message "<Invoke-ATMDFileDeployment> Копируем файл $($FilePair.source)"
                    Copy-Item -Path $FilePair.source -Destination $FilePair.destination -Force:([System.Convert]::ToBoolean($FilePair.forceReplace)) -ErrorAction Stop
                    Write-Verbose -Message "<Invoke-ATMDFileDeployment> Файл назначения - $($FilePair.destination)"
                    $Result.CopiedFiles += $FilePair.source
                }
                catch {
                    Write-Error -Message "При копировании файла $($FilePair.source) произшла ошибка $($PSItem.Exception.Message)"
                    $Result.ProblemItems += $FilePair.source
                    $Result.ProblemItems += $FilePair.destination
                    $Result.ExitCode = $Result.ExitCode -bor $RESULT_COPY_FILE_EXCEPTION
                }
            }
            #endregion Копируем файлы

            #region Обновляем файлы
            foreach ($FilePair in $FileDeployment.filesToUpdate) {
                try {
                    # Если исходного файла не будет, значит и делать с ним нечего.
                    if (Test-Path -Path $FilePair.source) {
                        # Тут мы должны определить, копировать файл или нет.
                        # Скрипты Powershell стоит копировать, если у них обновилась версия, а остальные файлы - исходя из даты изменения.
                        # По умолчанию считаем, что копировать не надо.
                        Write-Verbose -Message "<Invoke-ATMDFileDeployment> Обновление файлов - файл $($FilePair.source)"
                        $ShouldCopy = $false
                        # Если файла назначения нет - копируем.
                        if (-not(Test-Path -Path $FilePair.destination)) {
                            $ShouldCopy = $true
                            Write-Verbose -Message '<Invoke-ATMDFileDeployment> Файл назначения отсутствует.'
                        }
                        # Если же существует - смотрим, Powershell-скрипт это или нет
                        else {
                            # Получаем объекты самих файлов.
                            $SourceFile = Get-Item -Path $FilePair.source
                            $DestinationFile = Get-Item -Path $FilePair.destination
                            if ($SourceFile.Extension -eq '.ps1') {
                                try {
                                    $SourceScritpInfo = Test-ScriptFileInfo -Path $FilePair.source
                                }
                                catch {
                                    $SourceScritpInfo = $null
                                }
                                try {
                                    $DestinationScritpInfo = Test-ScriptFileInfo -Path $FilePair.destination
                                }
                                catch {
                                    $DestinationScritpInfo = $null
                                }

                                if ($SourceScritpInfo) {
                                    if (-not($DestinationScritpInfo)) {
                                        Write-Verbose -Message "<Invoke-ATMDFileDeployment> Версия исходного файла: $($SourceScritpInfo.Version) / Версия файла назначения: $($DestinationScritpInfo.Version)"
                                        if ($SourceScritpInfo.Version -gt $DestinationScritpInfo.Version) {
                                            Write-Verbose -Message '<Invoke-ATMDFileDeployment> Скрипт Powershell устарел...'
                                            $ShouldCopy = $true
                                        }
                                        else {
                                            Write-Verbose -Message "<Invoke-ATMDFileDeployment> Пропускаем скрипт $($FilePair.source)"
                                        }
                                    }
                                    else {
                                        Write-Verbose -Message '<Invoke-ATMDFileDeployment> Скрипт Powershell не имеет информации о версии - заменим его.'
                                        $ShouldCopy = $true
                                    }
                                }
                                else {
                                    Write-Warning -Message "Исходный скрипт $($FilePair.source) не имеет информации о версии - не будет обработан."
                                }
                            }
                            # Если не скрипт - ориентируемся на дату изменения.
                            else {
                                if (($DestinationFile.LastWriteTime -lt $SourceFile.LastWriteTime)) {
                                    Write-Verbose -Message '<Invoke-ATMDFileDeployment> Файл назанчения устарел...'
                                    $ShouldCopy = $true
                                }
                                else {
                                    Write-Verbose -Message "<Invoke-ATMDFileDeployment> Пропускаем файл $($FilePair.source)"
                                }
                            }
                        } # end of 'if (-not(Test-Path -Path $FilePair.destination)) {...} else {'
                    } # end of 'if (Test-Path -Path $FilePair.source) {'

                    # Если стало ясно, что надо копировать...
                    if ($ShouldCopy) {
                        # Проверяем, существует ли папка для целевого файла.
                        $DestinationFilePath = Split-Path -Path $FilePair.destination -Parent
                        if (-not(Test-Path -Path $DestinationFilePath)) {
                            New-Item -ItemType Directory -Path $DestinationFilePath
                        }
                        # Копируем файлы из списка
                        Write-Verbose -Message "<Invoke-ATMDFileDeployment> Копируем файл $($FilePair.source)"
                        Copy-Item -Path $FilePair.source -Destination $FilePair.destination -Force -ErrorAction Stop
                        Write-Verbose -Message "<Invoke-ATMDFileDeployment> Файл назначения - $($FilePair.destination)"
                        $Result.CopiedFiles += $FilePair.source
                    }
                }
                catch {
                    Write-Error -Message "При копировании файла $($FilePair.source) произшла ошибка $($PSItem.Exception.Message)"
                    $Result.ProblemItems += $FilePair.source
                    $Result.ProblemItems += $FilePair.destination
                    $Result.ExitCode = $Result.ExitCode -bor $RESULT_UPDATE_FILE_EXCEPTION
                }
            }
            #endregion Обновляем файлы
        }
        catch {
            # $PSCmdlet.ThrowTerminatingError($PSitem)
            Write-Error -Exception $PSItem.Exception
        }
    }

    end {
        if ($PassThru) {
            return $Result
        }
    }
}