Начало в заметке "Servers - Архивирование в AWS Glacier (часть 3)".
В этой заметке описывается скрипт для проверки содержимого облачного архива AWS Glacier и удаления локальных файлов, успешно размещенных в облаке.
Логика скрипта:
- Найти файл с номером задания.
- Подключиться к AWS Glacier и скачать результат инвентаризации коллекции архивов.
- В базе данных компании найти все архивы с состоянием transfered.
- Сравнить размеры локального архива и файла в облаке.
- Удалить локальный архив и пометить его состояние в базе данных как archived.
Код скрипта:
# Folder for saving archives
$ArchiveFolder = "C:\2_Transfer"
# Notifications
$MailServer = "mail.domain.com"
$MailEncoding = [System.Text.Encoding]::UTF8
$MailFrom = "$env:computername@domain.com"
$MailSubject = "Glacier backup"
[string[]]$MailTO = "admin@domain.com"
[string[]]$MailCC = ""
# AWS variables
$AWSAccountID = 'AWS_Account_ID'
$AWSRegion = 'AWS_region'
$AWSVaultName = 'AWS_Vault_Name'
$AWSProfileAccessKey = "AWS_Access_Key"
$AWSProfileSecretKey = "AWS_Secret_Key"
$AWSJobIdFileName = "AWSGlacier-InventoryJobId.txt"
# SQL variables
$SQLServer = 'SQLServer.domain.com'
$SQLDatabase = 'AWS-Glacier'
$SQLTable = '[archive-2017]'
$SQLUsername = 'db_user'
$SQLPassword = 'db_password'
# Registering AWS libraries
Add-Type -Path "C:\Program Files (x86)\AWS SDK for .NET\bin\Net45\AWSSDK.Core.dll"
Add-Type -Path "C:\Program Files (x86)\AWS SDK for .NET\bin\Net45\AWSSDK.Glacier.dll"
Function Write-ScriptLog {
Param(
[CmdletBinding()]
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$Message,
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$LogFile
)
Process {
$LogMessage = Get-Date -uformat "%d.%m.%Y %H:%M:%S"
$LogMessage += "`t"
$LogMessage += $Message
$LogMessage | Out-File -FilePath $LogFile -Append
}
}#End Function
Function Invoke-Query {
Param(
[CmdletBinding()]
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$ServerInstance,
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$Database,
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$Username,
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$Password,
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$Query
)
Process {
$ConnectionString = "Server=$ServerInstance;uid=$Username;pwd=$Password;Database=$Database;Integrated Security=False;"
$Connection = New-Object System.Data.SqlClient.SqlConnection
$Connection.ConnectionString = $ConnectionString
$Connection.Open()
$Command = New-Object System.Data.SQLClient.SQLCommand
$Command.Connection = $Connection
$Command.CommandText = $Query
$Result = $Command.ExecuteReader()
$Datatable = New-Object “System.Data.DataTable”
$Datatable.Load($Result)
$Connection.Close()
Return $Datatable
}
}#End Function
Function Invoke-NonQuery {
Param(
[CmdletBinding()]
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$ServerInstance,
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$Database,
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$Username,
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$Password,
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$Query
)
Process {
$ConnectionString = "Server=$ServerInstance;uid=$Username;pwd=$Password;Database=$Database;Integrated Security=False;"
$Connection = New-Object System.Data.SqlClient.SqlConnection
$Connection.ConnectionString = $ConnectionString
$Connection.Open()
$Command = New-Object System.Data.SQLClient.SQLCommand
$Command.Connection = $Connection
$Command.CommandText = $Query
$Result = $Command.ExecuteNonQuery()
$Connection.Close()
}
}#End Function
# --- Start ---
# Calculating variables
$CurrentDate = Get-Date
$ScriptFolder = $MyInvocation.MyCommand.Path.SubString(0,($MyInvocation.MyCommand.Path.Length `
- $MyInvocation.MyCommand.Name.Length))
$LogFile = $ScriptFolder + 'Logs\' + (Get-Date -format yyyy_MM_dd) + "_" `
+ $MyInvocation.MyCommand.Name.SubString(0,($MyInvocation.MyCommand.Name.Length - 4)) + ".log"
# Log-file creation
If (-not(Test-Path ($ScriptFolder + 'Logs') -PathType Container )) {
New-Item -ItemType Directory -Path ($ScriptFolder + 'Logs')
}
Out-File -FilePath $LogFile
Write-ScriptLog -LogFile $LogFile -Message ($MyInvocation.MyCommand.Name + " started")
Write-ScriptLog -LogFile $LogFile -Message ("============================== Input data ==============================")
Write-ScriptLog -LogFile $LogFile -Message ("AWS Account ID = $AWSAccountID")
Write-ScriptLog -LogFile $LogFile -Message ("AWS Region = $AWSRegion")
Write-ScriptLog -LogFile $LogFile -Message ("AWS Vault Name = $AWSVaultName")
Write-ScriptLog -LogFile $LogFile -Message ("AWS Job Id File Name = $AWSJobIdFileName")
Write-ScriptLog -LogFile $LogFile -Message ("============================== Processing ==============================")
# Input file name
$AWSJobIdFilePath = $ScriptFolder + $AWSJobIdFileName
$ProcessedArchives = $Nothing
$NotProcessedArchives = $Nothing
If (Test-Path($AWSJobIdFilePath)) {
Write-ScriptLog -LogFile $LogFile -Message ("Reading $AWSJobOutputFileName file")
$AWSGlacerJobId = Get-Content $AWSJobIdFilePath
Write-ScriptLog -LogFile $LogFile -Message ("Inventory retrieval job id is $AWSGlacerJobId")
Write-ScriptLog -LogFile $LogFile -Message ("Connecting to AWS")
$AWSEndpoint = [Amazon.RegionEndpoint]::GetBySystemName($AWSRegion)
# Set inventory job for a AWS Glacier vault
$AWSGlacierClient = [Amazon.Glacier.AmazonGlacierClient]::New($AWSProfileAccessKey, $AWSProfileSecretKey, $AWSEndpoint)
Write-ScriptLog -LogFile $LogFile -Message ("Connection to AWS Glacier is opened")
Write-ScriptLog -LogFile $LogFile -Message ("Requesting job output")
$AWSGlacierJobOutputRequest = [Amazon.Glacier.Model.GetJobOutputRequest]::new()
$AWSGlacierJobOutputRequest.AccountId = $AWSAccountID
$AWSGlacierJobOutputRequest.VaultName = $AWSVaultName
$AWSGlacierJobOutputRequest.JobId = $AWSGlacerJobId
Try {
$AWSGlacierOutputResult = $AWSGlacierClient.GetJobOutput($AWSGlacierJobOutputRequest)
Write-ScriptLog -LogFile $LogFile -Message ("Job output is received with status " + $AWSGlacierOutputResult.Status)
}
Catch {
Write-ScriptLog -LogFile $LogFile -Message ("----> Error: " + $_.Exception.Message)
}
If ($AWSGlacierOutputResult -ne $Nothing) {
Write-ScriptLog -LogFile $LogFile -Message ("Parsing job output")
[Byte[]]$buffer = New-Object System.Byte[] 4096
$EncodedText = New-Object -TypeName System.Text.ASCIIEncoding
$AWSGlacierOutputResultContent = $Nothing
While(($i = $AWSGlacierOutputResult.Body.Read($buffer, 0, $buffer.Length)) -ne 0) {
$AWSGlacierOutputResultContent += $EncodedText.GetString($buffer, 0, $i)
}
$AWSGlacierContent = $AWSGlacierOutputResultContent | ConvertFrom-Json
Write-ScriptLog -LogFile $LogFile -Message ("Job output is parsed")
Write-ScriptLog -LogFile $LogFile -Message ("There are " + $AWSGlacierContent.ArchiveList.Count + " archives in the vault")
$AWSGlacierClient.Dispose()
Write-ScriptLog -LogFile $LogFile -Message ("Connection to AWS Glacier is closed")
Write-ScriptLog -LogFile $LogFile -Message ("Updating archives statuses in the database")
# Requesting archives from the database
$Query = "SELECT * FROM " + $SQLTable + " WHERE [state] = 'transfered'"
$TransferedFiles = Invoke-Query -ServerInstance $SQLServer -Database $SQLDatabase -Username $SQLUsername `
-Password $SQLPassword -Query $Query
Write-ScriptLog -LogFile $LogFile -Message ("There are " + $TransferedFiles.Count + " archives transfered to AWS Glacier")
ForEach ($CurrentFile in $TransferedFiles) {
$CurrentFileId = $CurrentFile."id"
$CurrentFileName = $CurrentFile."archive-name"
$CurrentFileAwsId = $CurrentFile."aws-archive-id"
$CurrentFilePath = $ArchiveFolder + "\" + $CurrentFileName
# Looking for current archive id in the vault
Write-ScriptLog -LogFile $LogFile -Message (" $CurrentFileName is waiting for successfull transrefing confirmation")
ForEach ($AWSArchive in $AWSGlacierContent.ArchiveList) {
If ($AWSArchive.ArchiveId -eq $CurrentFileAwsId) {
Write-ScriptLog -LogFile $LogFile -Message (" $CurrentFileName is found is the vault")
If ((Get-Item $CurrentFilePath).Length -eq $AWSArchive.Size) {
Write-ScriptLog -LogFile $LogFile -Message (" The size of the archives at local server and at AWS are the same")
Try {
Remove-Item $CurrentFilePath -Force -Confirm:$False -ErrorAction Stop
Write-ScriptLog -LogFile $LogFile -Message (" $CurrentFilePath is deleted")
} Catch {
Write-ScriptLog -LogFile $LogFile -Message ("----> Error: " + $_.Exception.Message)
}
Write-ScriptLog -LogFile $LogFile -Message (" Updating the arhive status in the database")
$SQLAWSArchiveId = $AWSTransferResult.ArchiveId
$SQLAccessTimeStamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$SQLState = "archived"
$SQLNotice = "size: " + $AWSArchive.size + " bytes, creation: " + $AWSArchive.CreationDate
$UpdateQuery = "UPDATE " + $SQLTable + " SET [state] = '$SQLState',"
$UpdateQuery += " [notice] = '$SQLNotice', [last-access-timestamp] = '$SQLAccessTimeStamp' WHERE [id] = $CurrentFileId"
Invoke-NonQuery -ServerInstance $SQLServer -Database $SQLDatabase -Username $SQLUsername `
-Password $SQLPassword -Query $UpdateQuery
$ProcessedArchives += $CurrentFileName + "; "
} Else {
$FailedArchives += $CurrentFileName + "; "
}
}
}
}
Write-ScriptLog -LogFile $LogFile -Message ("Archives statuses are updated the database")
Send-MailMessage -SmtpServer $MailServer -Encoding $MailEncoding -From $MailFrom -To $MailTo -CC $MailCC `
-Subject $MailSubject -BodyAsHtml "Successfully processed archives: $ProcessedArchives<br>" + `
"Failed archives: $FailedArchives<br />Please review $LogFile on $env:computername for details."
Write-ScriptLog -LogFile $LogFile -Message ("Deleting $AWSJobIdFileName file")
Try {
Remove-Item $AWSJobIdFilePath -Force -Confirm:$False -ErrorAction Stop
Write-ScriptLog -LogFile $LogFile -Message ("$AWSJobIdFileName file is deleted")
} Catch {
Write-ScriptLog -LogFile $LogFile -Message ("----> Error: " + $_.Exception.Message)
}
} Else {
Write-ScriptLog -LogFile $LogFile -Message ("Job output is empty")
Send-MailMessage -SmtpServer $MailServer -Encoding $MailEncoding -From $MailFrom -To $MailTo -CC $MailCC `
-Subject $MailSubject -BodyAsHtml "AWS job output is empty<br />Please review $LogFile on $env:computername for details."
If (((Get-Date) - (get-item $AWSJobIdFilePath).CreationTime).Days -ge 2) {
Write-ScriptLog -LogFile $LogFile -Message ("$AWSJobIdFileName file is outdated")
Write-ScriptLog -LogFile $LogFile -Message ("Deleting $AWSJobIdFileName file")
Try {
Remove-Item $AWSJobIdFilePath -Force -Confirm:$False -ErrorAction Stop
Write-ScriptLog -LogFile $LogFile -Message ("$AWSJobIdFileName file is deleted")
} Catch {
Write-ScriptLog -LogFile $LogFile -Message ("----> Error: " + $_.Exception.Message)
}
}
}
}
Else {
Write-ScriptLog -LogFile $LogFile -Message ("The processing is stopped")
Write-ScriptLog -LogFile $LogFile -Message ("Use Process-AWSGlacierInventory.ps1 script to get information about AWS archives")
}
# --- End ---
Write-ScriptLog -LogFile $LogFile -Message ("========================================================================")
Write-ScriptLog -LogFile $LogFile -Message ($MyInvocation.MyCommand.Name + " stopped")
Входные данные скрипта:
- ArchiveFolder - путь, куда скрипт сохраняет ZIP-архивы
- MailServer - почтовый сервер компании для отправки уведомлений о работе скрипта
- MailEncoding - кодировка писем
- MailFrom - от чьего имени отправляются письма
- MailSubject - тема писем
- MailTO - кому отправлять письма
- MailCC - кому отправлять копии писем
- AWSAccountID - ID учетной записи Amazon Web Services
- AWSRegion - регион, где находится коллекция архивов
- AWSVaultName - имя коллекции архивов
- AWSProfileAccessKey - учетная запись AWS с правом доступа к коллекции архивов
- AWSProfileSecretKey - ключ к учетной записи AWS
- SQLServer - SQL сервер компании
- SQLDatabase - имя базы данных
- SQLTable - имя таблицы
- SQLUsername - имя пользователя с правом вносить изменения в SQL базу данных
- SQLPassword - пароль пользователя SQL
- AWSJobIdFileName - имя файла, в котором нужно сохранить номер задания (файл сохраняется в той же папке, где лежит скрипт)
Результат работы скрипта:
- Завершенная процедура архивирования файла в облаке AWS Glacier.
- Лог-файл работы скрипта.
Продолжение в заметке "Servers - Архивирование в AWS Glacier (часть 5)".
