Прошлый пост был посвящён скрипту, который резервирует файлы профиля пользователя непосредственно из интерактивного сеанса. Как уже говорилось ранее, данный метод бэкапинга себя отлично зарекомендовал, но он никуда не годится, если речь идёт о профиле выключенного доменного пользователя в рамках Active Directory.
Ещё интереснее складывается ситуация, если пользователь с логином (речь про sAMAccountName объекта) user01 был переименован в user02. В этом случае пользователь уже будет входить под учётными данными user02, однако профиль пользователя (включая все настройки) будет по-прежнему храниться в директории user01.
Такая путаница продиктована привязкой директории профиля к SID пользователя, который после смены sAMAccountName остаётся неизменным. В конечном итоге, чтобы получить путь к папке профиля переименованного пользователя нужно обращаться к реестру.
Профиль-лист пользователей находится в реестре по этому пути:
HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\
Именно там и следует искать SID нужного пользователя и выдёргивать значение параметра ProfileImagePath. Всё становится чуть проще если речь идёт об управлении хостами организации с помощью служб удалённого доступа. Любую рутину можно описать языком сценария. Так и появился данный метод резервирования данных.
# Заголовок окна консоли #
[System.Console]::Title = "Копирование папки профиля"
# Время задержки #
$timeout = 15
# Директория для резервного копирования #
$backuppath = "\\192.168.1.100\userprofiles" # <= Указывается сетевой путь
# Импорт модулей #
Import-Module ActiveDirectory
Import-Module ActiveDirectoryObjectPicker
# Получение имени удалённого хоста #
Write-Host -ForegroundColor Yellow "Ожидание имени удалённого хоста..."
$remotehost = (Show-ActiveDirectoryObjectPicker -AllowedLocations EnterpriseDomain -DefaultObjectTypes Computers -DefaultLocations EnterpriseDomain).Name
Write-Host -ForegroundColor Green "Удалённый хост: $remotehost"
Write-Host
# Получение имени пользователя #
Write-Host -ForegroundColor Yellow "Ожидание данных пользователя..."
$upn = (Show-ActiveDirectoryObjectPicker -AllowedLocations EnterpriseDomain -DefaultObjectTypes Users -DefaultLocations EnterpriseDomain).Upn
$username = ($upn -Split '@')[0]
Write-Host -ForegroundColor Green "Пользователь: $username"
Write-Host
# Получение SID пользователя #
Write-Host -ForegroundColor Yellow "Получение SID пользователя..."
$sid = (Get-ADUser -Identity $username | Select-Object -ExpandProperty SID).Value
Write-Host -ForegroundColor Green "SID пользователя: $sid"
Write-Host
# Получение директории профиля и копирование файлов #
$pingremotehost = Test-Connection -ComputerName $remotehost -Count 2 -Quiet
if ($pingremotehost)
{
try
{
# Получение директории пользователя из реестра #
Write-Host -ForegroundColor Yellow "Получение директории профиля пользователя..."
$regpath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid"
$profilepath = Invoke-Command -ErrorAction Stop -ComputerName $remotehost -ScriptBlock {
param($path)
(Get-ItemProperty -Path $path).ProfileImagePath
} -ArgumentList $regpath
$modifiedPath = "\\$remotehost\$profilepath" -replace ":", "$"
Write-Host -ForegroundColor Green "Найдена директория профиля пользователя: $modifiedPath"
}
catch
{
Write-Host -ForegroundColor Red "Удалённый реестр узла $remotehost не доступен!"
Start-Sleep($timeout)
return
}
$testpath = Test-Path -Path $backuppath
if ($testpath)
{
Write-Host
Write-Host -ForegroundColor Yellow "Запуск резервного копирования файлов профиля..."
# Создание директории с именем пользователя #
$backupprofile = Join-Path -Path $backuppath -ChildPath "$username - $remotehost"
New-Item -Path $backupprofile -ItemType Directory -Force | Out-Null
# Определение массива папок, которые нужно скопировать #
$foldersToCopy = @( "Desktop", "Documents", "Downloads", "Pictures", "Music" )
# Общая информация для прогресса #
$totalFolders = $foldersToCopy.Count
$currentFolderIndex = 0
# Копирование файлов #
foreach ($folder in $foldersToCopy)
{
$currentFolderIndex++
$sourceFolder = Join-Path -Path $modifiedPath -ChildPath $folder
$targetFolder = Join-Path -Path $backupprofile -ChildPath $folder
# Устанавливаем заголовок прогресса
$progressMessage = "Копирование $folder..."
# Отображаем прогресс #
Write-Progress -Activity "Копирование папок" -Status $progressMessage -PercentComplete (($currentFolderIndex / $totalFolders) * 100)
# Копируем папку, если она существует #
if (Test-Path -Path $sourceFolder)
{
try
{
# Создаем целевую папку, если она не существует #
if (!(Test-Path -Path $targetFolder))
{ New-Item -ItemType Directory -Path $targetFolder -ErrorAction Stop }
# Получаем файлы для копирования, исключая desktop.ini и thumbs.db #
$filesToCopy = Get-ChildItem -Path $sourceFolder -Recurse | Where-Object { $_.Name -ne 'desktop.ini' -and $_.Name -ne 'thumbs.db' }
# Копируем файлы #
$totalFiles = $filesToCopy.Count
$currentFileIndex = 0
# Цикл копирования #
foreach ($file in $filesToCopy)
{
$currentFileIndex++
$destinationFile = Join-Path -Path $targetFolder -ChildPath $file.FullName.Substring($sourceFolder.Length + 1)
$destinationFileDir = Split-Path -Path $destinationFile -Parent
# Создаем директорию файла, если она не существует #
if (!(Test-Path -Path $destinationFileDir))
{ New-Item -ItemType Directory -Path $destinationFileDir -Force -ErrorAction Stop }
# Копируем файл #
Copy-Item -Path $file.FullName -Destination $destinationFile -Force -ErrorAction Stop
# Отображаем прогресс по файлам #
Write-Progress -Activity "Копирование файлов" -Status "Копирование $($file.Name)" -PercentComplete (($currentFileIndex / $totalFiles) * 100)
}
}
catch
{
Write-Host -ForegroundColor Red "Во время операции копирования произошла ошибка!"
Start-Sleep($timeout)
return
}
}
}
}
else
{
Write-Host -ForegroundColor Red "Пути $backuppath не существует!"
Start-Sleep($timeout)
return
}
# Завершение копирования #
Write-Progress -Activity "Копирование папок" -Status "Завершено!" -Completed
Start-Sleep($timeout)
return
}
else
{
Write-Host -ForegroundColor Red "Удалённый хост $remotehost не отвечает!"
Start-Sleep($timeout)
return
}
Логика работы этого ps1 проста: сначала запрашивается имя компьютера в домене, потом имя пользователя, далее скрипт получает SID пользователя и находит путь к директории профиля пользователя. Полученный путь преобразовывается в сетевой и скрипт начинает копирование папок, упомянутых в массиве $foldersToCopy.
Весь алгоритм работы скрипта можно показать в виде схемы:

Скрипт взаимодействует со службами Active Directory с помощью модуля ActiveDirectoryObjectPicker. Предполагается, что и он, и модуль ActiveDirectory уже установлены в системе, из которой осуществляется запуск.
Ну, и само собой, требуется учётная запись с правами администратора, которая бы имела доступ к административным ресурсам удалённых хостов.
В самом же скрипте нужно только указать корректный и доступный для записи путь в переменной $backuppath.