244 lines
9.2 KiB
PowerShell
244 lines
9.2 KiB
PowerShell
[CmdletBinding(SupportsShouldProcess = $true)]
|
|
param(
|
|
[string]$OldInstallRoot = 'C:\Program Files\ActivityWatch-Phase2',
|
|
[string]$OldStateRoot = 'C:\ProgramData\ActivityWatch-Phase2',
|
|
[string]$NewInstallRoot = 'C:\Program Files\AWatch-rus\bin',
|
|
[string]$NewStateRoot = 'C:\ProgramData\AWatch-rus',
|
|
[string]$ToolkitRoot = 'C:\Program Files\AWatch-rus\windows',
|
|
[switch]$SkipValidation
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$modulePath = Join-Path $PSScriptRoot 'ActivityWatch.Windows.Common.psm1'
|
|
Import-Module $modulePath -Force
|
|
|
|
Assert-Administrator
|
|
|
|
function Copy-DirectoryContents {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Source,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Destination
|
|
)
|
|
|
|
if (-not (Test-Path -LiteralPath $Source)) {
|
|
return
|
|
}
|
|
|
|
New-ActivityWatchDirectory -Path $Destination
|
|
Copy-Item -Path (Join-Path $Source '*') -Destination $Destination -Recurse -Force
|
|
}
|
|
|
|
function Copy-IfExists {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Source,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Destination
|
|
)
|
|
|
|
if (Test-Path -LiteralPath $Source) {
|
|
Copy-Item -LiteralPath $Source -Destination $Destination -Force
|
|
}
|
|
}
|
|
|
|
function Convert-PathValue {
|
|
param(
|
|
[AllowNull()]
|
|
[string]$Value
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Value)) {
|
|
return $Value
|
|
}
|
|
|
|
return $Value.Replace($OldInstallRoot, $NewInstallRoot).Replace($OldStateRoot, $NewStateRoot)
|
|
}
|
|
|
|
function Stop-AWatchTaskSet {
|
|
foreach ($task in @(Get-ScheduledTask | Where-Object { $_.TaskName -eq 'ActivityWatch Recovery' -or $_.TaskName -like 'ActivityWatch Launch *' })) {
|
|
Stop-ScheduledTask -TaskName $task.TaskName -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
function Get-ExistingAWatchConfig {
|
|
$newConfigPath = Join-Path $NewStateRoot 'deployment-config.json'
|
|
$oldConfigPath = Join-Path $OldStateRoot 'deployment-config.json'
|
|
|
|
if (Test-Path -LiteralPath $oldConfigPath) {
|
|
return [pscustomobject]@{
|
|
Path = $oldConfigPath
|
|
Config = Read-ActivityWatchDeploymentConfig -Path $oldConfigPath
|
|
}
|
|
}
|
|
|
|
if (Test-Path -LiteralPath $newConfigPath) {
|
|
return [pscustomobject]@{
|
|
Path = $newConfigPath
|
|
Config = Read-ActivityWatchDeploymentConfig -Path $newConfigPath
|
|
}
|
|
}
|
|
|
|
throw "Не найден deployment-config.json ни в $OldStateRoot, ни в $NewStateRoot."
|
|
}
|
|
|
|
function Update-AWatchConfigPaths {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[pscustomobject]$Config
|
|
)
|
|
|
|
$logsRoot = Join-Path $NewStateRoot 'logs'
|
|
$Config.paths.installRoot = $NewInstallRoot
|
|
$Config.paths.stateRoot = $NewStateRoot
|
|
$Config.paths.logsRoot = $logsRoot
|
|
$Config.paths.collectorScript = Join-Path $NewStateRoot 'browser-domains-native-collector.ps1'
|
|
$Config.paths.endpointCollectorScript = Join-Path $NewStateRoot 'dlp-endpoint-signals-collector.ps1'
|
|
if ($Config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') {
|
|
$Config.paths.sessionCollectorScript = Join-Path $NewStateRoot 'worktime-session-collector.ps1'
|
|
}
|
|
$Config.paths.rulesPath = Join-Path $NewStateRoot 'web-category-rules.json'
|
|
if ($Config.paths.PSObject.Properties.Name -contains 'policyPath') {
|
|
$Config.paths.policyPath = Join-Path $NewStateRoot 'dlp-policy.json'
|
|
}
|
|
$Config.paths.launchScript = Join-Path $NewStateRoot 'launch-watchers.ps1'
|
|
$Config.paths.recoveryScript = Join-Path $NewStateRoot 'recovery-loop.ps1'
|
|
|
|
if ($Config.PSObject.Properties.Name -contains 'incidentCapture' -and $Config.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') {
|
|
$Config.incidentCapture.artifactsRoot = Convert-PathValue -Value ([string]$Config.incidentCapture.artifactsRoot)
|
|
}
|
|
|
|
return $Config
|
|
}
|
|
|
|
$existing = Get-ExistingAWatchConfig
|
|
$backupRoot = Join-Path $NewStateRoot ('migration-backups\' + (Get-Date -Format 'yyyyMMdd-HHmmss'))
|
|
$newConfigPath = Join-Path $NewStateRoot 'deployment-config.json'
|
|
$newLogsRoot = Join-Path $NewStateRoot 'logs'
|
|
|
|
$summary = [ordered]@{
|
|
sourceConfig = $existing.Path
|
|
oldInstallRoot = $OldInstallRoot
|
|
oldStateRoot = $OldStateRoot
|
|
newInstallRoot = $NewInstallRoot
|
|
newStateRoot = $NewStateRoot
|
|
backupRoot = $backupRoot
|
|
actions = @(
|
|
'stop ActivityWatch scheduled tasks',
|
|
'backup old/new install and state directories',
|
|
'copy old install/state contents to AWatch-rus paths',
|
|
'rewrite deployment-config.json paths',
|
|
'regenerate launcher/recovery scripts',
|
|
're-register scheduled tasks',
|
|
'run validate-deployment.ps1'
|
|
)
|
|
}
|
|
|
|
if ($WhatIfPreference) {
|
|
return [pscustomobject]$summary
|
|
}
|
|
|
|
if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, 'Миграция ActivityWatch Windows/RDP путей в AWatch-rus')) {
|
|
New-ActivityWatchDirectory -Path $NewStateRoot
|
|
New-ActivityWatchDirectory -Path $backupRoot
|
|
|
|
Stop-AWatchTaskSet
|
|
|
|
foreach ($item in @(
|
|
@{ Source = $OldInstallRoot; Name = 'old-install' },
|
|
@{ Source = $OldStateRoot; Name = 'old-state' },
|
|
@{ Source = $NewInstallRoot; Name = 'new-install' },
|
|
@{ Source = $NewStateRoot; Name = 'new-state' }
|
|
)) {
|
|
if (Test-Path -LiteralPath $item.Source) {
|
|
$backupDest = Join-Path $backupRoot $item.Name
|
|
New-ActivityWatchDirectory -Path $backupDest
|
|
|
|
$excludeDirs = @()
|
|
if ($item.Source -eq $NewStateRoot) {
|
|
# Avoid infinite recursion: backupRoot is inside NewStateRoot by default.
|
|
$excludeDirs += $backupRoot
|
|
}
|
|
|
|
$robocopyArgs = @(
|
|
$item.Source,
|
|
$backupDest,
|
|
'/E',
|
|
'/R:1',
|
|
'/W:1',
|
|
'/NFL',
|
|
'/NDL',
|
|
'/NJH',
|
|
'/NJS',
|
|
'/NP'
|
|
)
|
|
if ($excludeDirs.Count -gt 0) {
|
|
$robocopyArgs += '/XD'
|
|
$robocopyArgs += $excludeDirs
|
|
}
|
|
|
|
& robocopy @robocopyArgs | Out-Null
|
|
if ($LASTEXITCODE -ge 8) {
|
|
throw "Backup robocopy failed (exit=$LASTEXITCODE) for source '$($item.Source)' to '$backupDest'"
|
|
}
|
|
}
|
|
}
|
|
|
|
Copy-DirectoryContents -Source $OldInstallRoot -Destination $NewInstallRoot
|
|
Copy-DirectoryContents -Source $OldStateRoot -Destination $NewStateRoot
|
|
New-ActivityWatchDirectory -Path $newLogsRoot
|
|
|
|
foreach ($file in @(
|
|
'browser-domains-native-collector.ps1',
|
|
'dlp-endpoint-signals-collector.ps1',
|
|
'worktime-session-collector.ps1',
|
|
'web-category-rules.example.json',
|
|
'dlp-policy.example.json'
|
|
)) {
|
|
Copy-IfExists -Source (Join-Path $ToolkitRoot $file) -Destination (Join-Path $NewStateRoot $file)
|
|
}
|
|
|
|
Copy-IfExists -Source (Join-Path $OldStateRoot 'web-category-rules.json') -Destination (Join-Path $NewStateRoot 'web-category-rules.json')
|
|
Copy-IfExists -Source (Join-Path $OldStateRoot 'dlp-policy.json') -Destination (Join-Path $NewStateRoot 'dlp-policy.json')
|
|
if (-not (Test-Path -LiteralPath (Join-Path $NewStateRoot 'web-category-rules.json'))) {
|
|
Copy-IfExists -Source (Join-Path $NewStateRoot 'web-category-rules.example.json') -Destination (Join-Path $NewStateRoot 'web-category-rules.json')
|
|
}
|
|
if (-not (Test-Path -LiteralPath (Join-Path $NewStateRoot 'dlp-policy.json'))) {
|
|
Copy-IfExists -Source (Join-Path $NewStateRoot 'dlp-policy.example.json') -Destination (Join-Path $NewStateRoot 'dlp-policy.json')
|
|
}
|
|
|
|
$config = Update-AWatchConfigPaths -Config $existing.Config
|
|
Write-ActivityWatchDeploymentConfig -Config $config -Path $newConfigPath
|
|
Write-ActivityWatchLaunchScript -Path $config.paths.launchScript -ConfigPath $newConfigPath
|
|
Write-ActivityWatchRecoveryScript -Path $config.paths.recoveryScript -ConfigPath $newConfigPath
|
|
|
|
$taskDefinitions = @($config.userTasks)
|
|
Set-ActivityWatchAcl -InstallRoot $NewInstallRoot -StateRoot $NewStateRoot -LogsRoot $newLogsRoot
|
|
Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptPath $config.paths.launchScript -ConfigPath $newConfigPath
|
|
Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $config.paths.recoveryScript -ConfigPath $newConfigPath
|
|
Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName
|
|
Start-Sleep -Seconds 5
|
|
|
|
if (-not $SkipValidation) {
|
|
$validateScript = Join-Path $ToolkitRoot 'validate-deployment.ps1'
|
|
if (-not (Test-Path -LiteralPath $validateScript)) {
|
|
$validateScript = Join-Path $PSScriptRoot 'validate-deployment.ps1'
|
|
}
|
|
$report = & $validateScript -ConfigPath $newConfigPath
|
|
if (-not [bool]$report.overallOk) {
|
|
throw "Миграция выполнена, но validation завершился ошибкой. Backup: $backupRoot"
|
|
}
|
|
}
|
|
|
|
[pscustomobject]@{
|
|
migrated = $true
|
|
backupRoot = $backupRoot
|
|
configPath = $newConfigPath
|
|
installRoot = $NewInstallRoot
|
|
stateRoot = $NewStateRoot
|
|
}
|
|
}
|