#powershell
#powershell
Вопрос:
Я боролся со своим скриптом для резервного копирования моих баз данных SQL и возврата различной информации в HTML-файл для целей отчетности. Я пытаюсь зафиксировать некоторые конкретные детали при выполнении резервного копирования, а затем позже извлечь остальную информацию из самого файла резервной копии. Когда я запускаю скрипт в его различных формах на одном сервере, он запускает run, когда я пытаюсь запустить его в списке серверов, он не работает.
Set-Location \DBPowershellSQLBackups
. .SQLBackupFunctions.ps1
$JobFunction=
{
function backup_server_dbs
{
#backup function takes server name as a paramenter
param([String]$ServerName)
#Load SMO features for powershell
Push-Location
Import-Module "SQLPS" -DisableNameChecking
Pop-Location
$nl = [Environment]::NewLine
#create connection to SQL server, ensure timeout set to 25 minutes (default is 10 minutes which is too short for backups)
$SQLSvr = New-Object -TypeName Microsoft.SQLServer.Management.Smo.Server($ServerName)
$SQLSvr.ConnectionContext.StatementTimeout = 0
#create database object type to be called in backup script
$Db = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database
$BackupFolder = "\DBSQLBACKUPS$($ServerName)"
$output = @{}
#for each database on the SQL server instance (excluding system databases)
foreach ($Db in $SQLSvr.Databases | Where-Object {@("tempdb", "model", "msdb", "ReportServer", "ReportServerTempDB") -notcontains $_.Name })
{
$BackupSetDescription = "Full Backup of " $Db.Name
$BackupFileSuffix = ".dat"
#set backup file name
$BackupName = $BackupFolder $Db.Name $BackupFileSuffix
$errorcatch = 0
$btmp = @{}
#useful error checking
#try to backup the sql database
try
{
Backup-SqlDatabase -ServerInstance $ServerName -Database $Db.Name -BackupFile $BackupName -BackupSetDescription $BackupSetDescription -Initialize
}#end try
#catch any errors, add them to the output variable and set the error variable to 1
Catch [System.Exception]
{
$btmp["Info"] = ($error[0].ToString())
$btmp["Status"] = "Failed"
$btmp["DatabaseName"] = $db.name
$btmp["Serveranem"] = $ServerName
$output.Add("$($servername)$($db.name)", $btmp)
$errorcatch = 1
}#end catch
#check error variable, if it is not 1, backup is successful and add success message to output variable, if not return output variable containing error information
Finally
{
if ($errorcatch -ne 1)
{
$btmp["Info"] = "No information"
$btmp["Status"] = "Success"
$btmp["DatabaseName"] = $db.name
$btmp["Serveranem"] = $ServerName
$output.Add("$($servername)$($db.name)", $btmp)
}#end if statement
$errorcatch = 0
}#end finally statement
}#end for each
return $output
}#end function backup_server_dbs
}#end job function
#set up parameters for running jobs
$ScriptStartTime = Get-Date
$BackupFileSuffix = ""
$servers = @("server1",
"server2",
"server3",
"server4",
"server5")
$jobs = @()
foreach($servername in $servers)
{
#set location of backup folder
$BackupFolder = "\DBSQLBACKUPS$($servername)"
#test the location of the backup folder, if it does not exist then create the location
if ((Test-Path $BackupFolder) -eq $False)
{
New-Item $BackupFolder -type Directory -Force
}#end if statement
}
#for each server in servers array run job function script block, add outputs to $jobs array
foreach ($servername in $servers)
{
$jobs = Start-job -ScriptBlock {backup_server_dbs $args[0]} -InitializationScript $JobFunction -ArgumentList($servername) -Name $servername
}#end for each statement
$jobs | Wait-Job
$nl = [Environment]::NewLine
$backupfiles = @()
$backups = @()
$finalreport = $null
$fulloutput = @{}
$index = 0
#for each job in jobs array simultaneously display the information and add it to the joutput variable (Tee-object), then remove the job from the array
foreach ($servername in $servers)
{
Receive-Job -Name $servername | Tee-Object -Variable joutput
$fulloutput.Add("$($servername)", $joutput)
#retrieve information from output and backup files themselves for report
$backupsummary = $null
$htmltableheader = "<p>
<h2 align=""left"">$($servername)</h2>
<table>
<tr>
<th>Database</th>
<th>Result</th>
<th>Time Completed</th>
<th>Information</th>
<th>Size</th>
</tr>"
$backupsummary = $htmltableheader
$BackupFolder = "\DBSQLBACKUPS$($servername)"
$BackupFiles = Get-SQLBackupFiles -FilePath $BackupFolder -SQLServer $($servername)
foreach($File in $BackupFiles)
{
$Backups = Get-BackupContents -FilePath $file -SQLServer $($servername)
$htmltablerow = "<tr>"
$key = "$($servername)$($backups.databasename)"
if($fulloutput.Item($servername).Item($key).Item("Status") -eq "Failed")
{
$htmltablerow = $htmltablerow "<td class=""fail"">$($backups.DatabaseName)</td>"
$htmltablerow = $htmltablerow "<td class=""fail"">Failed</td>"
$htmltablerow = $htmltablerow "<td class=""fail"">$($backups.BackupFinishDate)</td>"
$htmltablerow = $htmltablerow "<td class=""fail"">$($fulloutput.Item($key).Item("Info")) </td>"
$htmltablerow = $htmltablerow "<td class=""fail"">$([math]::Round($backups.BackupSize / 1MB))</td>"
}
else
{
$htmltablerow = $htmltablerow "<td class=""pass"">$($backups.DatabaseName)</td>"
$htmltablerow = $htmltablerow "<td class=""pass"">Success</td>"
$htmltablerow = $htmltablerow "<td class=""pass"">$($backups.BackupFinishDate)</td>"
$htmltablerow = $htmltablerow "<td class=""pass"">$($fulloutput.Item($key).Item("Info")) </td>"
$htmltablerow = $htmltablerow "<td class=""pass"">$([math]::Round($backups.BackupSize / 1MB))</td>"
}
$htmltablerow = $htmltablerow "</tr>"
$backupsummary = $htmltablerow
}
$backupsummary = "</table>
</p>"
$finalreport = $backupsummary
Remove-Job -Name $servername
}
Выдается эта ошибка
Method invocation failed because [System.Object[]] doesn't contain a method named 'Item'.
At \DBPowershellSQLBackupsSQLBackupsFull.ps1:176 char:46
if($fulloutput.Item($servername).Item <<<< ($key).Item("Status") -eq "Failed")
CategoryInfo : InvalidOperation: (Item:String) [], RuntimeException
FullyQualifiedErrorId : MethodNotFound
Если я немного изменю сценарий и изменю часть, которая возвращает информацию о задании, на это
#for each job in jobs array simultaneously display the information and add it to the joutput variable (Tee-object), then remove the job from the array
foreach ($servername in $servers)
{
Receive-Job -Name $servername | Tee-Object -Variable joutput
#retrieve information from output and backup files themselves for report
$backupsummary = $null
$htmltableheader = "<p>
<h2 align=""left"">$($servername)</h2>
<table>
<tr>
<th>Database</th>
<th>Result</th>
<th>Time Completed</th>
<th>Information</th>
<th>Size</th>
</tr>"
$backupsummary = $htmltableheader
$BackupFolder = "\ad.mitsubishi-trust.co.ukMUTBROOTDBSQLBACKUPS$($servername)"
$BackupFiles = Get-SQLBackupFiles -FilePath $BackupFolder -SQLServer $($servername)
foreach($File in $BackupFiles)
{
$Backups = Get-BackupContents -FilePath $file -SQLServer $($servername)
$htmltablerow = "<tr>"
$key = "$($servername)$($backups.databasename)"
if($joutput.Item($key).Item("Status") -eq "Failed")
{
$htmltablerow = $htmltablerow "<td class=""fail"">$($backups.DatabaseName)</td>"
$htmltablerow = $htmltablerow "<td class=""fail"">Failed</td>"
$htmltablerow = $htmltablerow "<td class=""fail"">$($backups.BackupFinishDate)</td>"
$htmltablerow = $htmltablerow "<td class=""fail"">$($joutput.Item($key).Item("Info")) </td>"
$htmltablerow = $htmltablerow "<td class=""fail"">$([math]::Round($backups.BackupSize / 1MB))</td>"
}
else
{
$htmltablerow = $htmltablerow "<td class=""pass"">$($backups.DatabaseName)</td>"
$htmltablerow = $htmltablerow "<td class=""pass"">Success</td>"
$htmltablerow = $htmltablerow "<td class=""pass"">$($backups.BackupFinishDate)</td>"
$htmltablerow = $htmltablerow "<td class=""pass"">$($joutput.Item($key).Item("Info")) </td>"
$htmltablerow = $htmltablerow "<td class=""pass"">$([math]::Round($backups.BackupSize / 1MB))</td>"
}
$htmltablerow = $htmltablerow "</tr>"
$backupsummary = $htmltablerow
}
$backupsummary = "</table>
</p>"
$finalreport = $backupsummary
Remove-Job -Name $servername
}
Вместо этого я получаю следующую ошибку, и все попытки, которые я предпринимал, чтобы заставить это работать, обычно приводят к этой ошибке:
You cannot call a method on a null-valued expression.
At \DBPowershellSQLBackupsSQLBackupsFull.ps1:170 char:36
if($joutput.Item($key).Item <<<< ("Status") -eq "Failed")
CategoryInfo : InvalidOperation: (Item:String) [], RuntimeException
FullyQualifiedErrorId : InvokeMethodOnNull
Я уже несколько дней бьюсь об это головой и не могу понять, почему это работает, когда массив $servers содержит только одно имя, а не когда он содержит несколько имен. Я также попытался изменить foreach, содержащий командлет Receive-Job, чтобы вызывать каждое $job в $jobs, а затем использовать $job.name (возможно ли это вообще??) однако вместо $servername это также не работает и выдает указанную выше ошибку с нулевым значением.
РЕДАКТИРОВАТЬ *****************
Спасибо за помощь в комментариях решение заключалось в сочетании использования
$joutput = Receive-job....
вместо Tee-объекта, а также с использованием
if($joutput.$($key).Status -eq "Failed")
вместо вызова .Метод элемента
Комментарии:
1. Вы действительно должны свести к минимуму текст, размещенный на меньшем фрагменте кода, который имеет отношение к рассматриваемой проблеме — 231 строка для первого блока кода !?! Если ваш код легче читать, то больше людей, вероятно, помогут вам. Повторная вторая ошибка:
$joutput
нигде не определена в вашем скрипте, поэтому вы получаетеYou cannot call a method on a null-valued expression.
2. $joutput определяется Tee-объектом из Receive-Job, он работает с одним именем сервера в массиве, но не с несколькими именами.
3. Прошу прощения за большой блок кода, но я не был уверен, что из него удалить, поскольку все это имеет отношение к моей проблеме?
4. Вместо того, чтобы использовать Tee-Object, вы просто пытались присвоить результат $joutput ? Например, $joutput = Receive-Job -name $имя_сервера