Резервное копирование Powershell SQL с использованием заданий и хэш-таблиц

#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 $имя_сервера