从后台作业获取输出

Getting output from background jobs

我创建了以下脚本来重置特定主机文件中所有计算机上本地管理员帐户的密码。此脚本正常运行并提供有用的输出,但速度很慢,因为它一次只处理一台机器。

# function to convert a secure string to a standard string
function ConvertTo-String {
    param(
        [System.Security.SecureString] $secureString
    )

    $marshal = [System.Runtime.InteropServices.Marshal]

    try {
        $intPtr = $marshal::SecureStringToBSTR($secureString)
        $string = $marshal::PtrToStringAuto($intPtr)
    }
    finally {
        if($intPtr) {
            $marshal::ZeroFreeBSTR($intPtr)
        }
    }
    $string
}

$clients = Get-Content -Path C:\scripts\utilities\hostnames_online.txt
$adminUser = "Administrator"

# prompt for password and confirm
do {
    $ss1 = Read-Host "Enter new password" -AsSecureString
    $ss2 = Read-Host "Enter again to confirm" -AsSecureString

# compare strings - proceed if same - prompt again if different
    $ok = (ConvertTo-String $ss1) -ceq (ConvertTo-String $ss2)
    Write-Host "Passwords match"

    if(-not $ok) {
        Write-Host "Passwords do not match"
    }
}
until($ok)

# set password variable to string value
$adminPassword = ConvertTo-String $ss1

# setup job to reset password on each client
foreach($client in $clients) {
    $status = "OFFLINE"
    $isOnline = "OFFLINE"

    if((Test-Connection -ComputerName $client -Quiet -Count 1 -Delay 1) -eq $true) {
        $isOnline = "ONLINE"
    }

    # change the password
    try {
        $localAdminAccount = [adsi]"WinNT://$client/$adminuser,user"
        $localAdminAccount.SetPassword($adminPassword)
        $localAdminAccount.SetInfo()
        Write-Verbose "Password change completed successfully"
    }
    catch {
        $status = "FAILED"
        Write-Verbose "Failed to change password"
    }

    # create psobject with system info
    $obj = New-Object -TypeName PSObject -Property @{
        ComputerName = $client
        Online = $isOnline
        ChangeStatus = $status
    }

    $obj | Select computerName, Online, changeStatus | Out-File -FilePath C:\test.txt -Append

    if($status -eq "FAILED" -or $isOnline -eq "OFFLINE") {
        $stream.writeline("$client -t $status")
    }
}

$adminPassword = " "

Write-Verbose "Complete"

Invoke-Item C:\test.txt

为了使脚本 运行 更快,我将其设置为使用后台作业,这样它就可以同时在多个客户端上 运行。但是,现在我的文本文件中没有输出。新脚本如下。第 43 行和第 79-89 行是更改。我需要对此脚本进行哪些更改以提供它在第一个版本中所做的输出?我尝试了很多其他方法来获取输出,而不是我目前在第 89 行的方法。

# function to convert a secure string to a standard string
function ConvertTo-String {
    param(
        [System.Security.SecureString] $secureString
    )

    $marshal = [System.Runtime.InteropServices.Marshal]

    try {
        $intPtr = $marshal::SecureStringToBSTR($secureString)
        $string = $marshal::PtrToStringAuto($intPtr)
    }
    finally {
        if($intPtr) {
            $marshal::ZeroFreeBSTR($intPtr)
        }
    }
    $string
}

$clients = Get-Content -Path C:\scripts\utilities\hostnames_online.txt
$adminUser = "Administrator"

# prompt for password and confirm
do {
    $ss1 = Read-Host "Enter new password" -AsSecureString
    $ss2 = Read-Host "Enter again to confirm" -AsSecureString

# compare strings - proceed if same - prompt again if different
    $ok = (ConvertTo-String $ss1) -ceq (ConvertTo-String $ss2)
    Write-Host "Passwords match"

    if(-not $ok) {
        Write-Host "Passwords do not match"
    }
}
until($ok)

# set password variable to string value
$adminPassword = ConvertTo-String $ss1

# setup job to reset password on each client
#-----------------------------------------------------------------------------------------
$scriptBlock = {
#-----------------------------------------------------------------------------------------
    foreach($client in $clients) {
        $status = "OFFLINE"
        $isOnline = "OFFLINE"

        if((Test-Connection -ComputerName $client -Quiet -Count 1 -Delay 1) -eq $true) {
            $isOnline = "ONLINE"
        }

        # change the password
        try {
            $localAdminAccount = [adsi]"WinNT://$client/$adminuser,user"
            $localAdminAccount.SetPassword($adminPassword)
            $localAdminAccount.SetInfo()
            Write-Verbose "Password change completed successfully"
        }
        catch {
            $status = "FAILED"
            Write-Verbose "Failed to change password"
        }

        # create psobject with system info
        $obj = New-Object -TypeName PSObject -Property @{
            ComputerName = $client
            Online = $isOnline
            ChangeStatus = $status
        }

        $obj | Select computerName, Online, changeStatus | Out-File -FilePath C:\test.txt -Append

        if($status -eq "FAILED" -or $isOnline -eq "OFFLINE") {
            $stream.writeline("$client -t $status")
        }
    }
}
#-----------------------------------------------------------------------------------------
Get-Job | Remove-Job -Force

Start-Job $scriptBlock -ArgumentList $_ -Name AdminPWReset

Get-Job

While(Get-Job -State "Running") {
    Start-Sleep -m 10
}

Receive-Job -name AdminPWReset | Out-File C:\test2.txt
#-----------------------------------------------------------------------------------------
$adminPassword = " "

Write-Verbose "Complete"

Invoke-Item C:\test.txt
Invoke-Item C:\test2.txt

您没有收到输出,因为您将整个循环移到了脚本块中:

$scriptBlock = {
    foreach ($client in $clients) {
        ...
    }
}

但在脚本块外部初始化了您的客户列表($clients),因此变量$clients 内部] 脚本块是一个不同的(空)变量,因为它在不同的范围内。因此,您的作业正在遍历一个空列表,因此不会产生任何输出。

为了能够在脚本块中使用客户端列表,您必须使用 using: 范围修饰符:

$scriptBlock = {
    foreach ($client in $using:clients) {
        ...
    }
}

或将客户端列表作为参数传递到脚本块中:

$scriptBlock = {
    foreach ($client in $args[0]) {
        ...
    }
}

Start-Job -ScriptBlock $scriptBlock -ArgumentList $clients

正如我从您的代码中看到的,您正在尝试执行后者:

Start-Job $scriptBlock -ArgumentList $_ -Name AdminPWReset

但是,由于两个原因,这不起作用:

  • 在您 运行 Start-Job 的上下文中没有当前对象,因此 $_ 是空的,没有任何内容被传递到脚本块中。
  • 脚本块既没有 Param() 块,也不使用自动变量 $args,因此即使您将参数传递给脚本块,它也永远不会被使用。

话虽如此,您首先不想传递 $clients,因为即使它有效,它也不会加快任何速度,因为仍然会处理整个客户端列表 依次。您真正想要做的是并行处理列表 。为此,您必须只将测试放入脚本块,然后循环为每个客户端启动一个作业:

$scriptBlock = {
    Param($client)

    $status = "OFFLINE"
    $isOnline = "OFFLINE"

    if (Test-Connection -Computer $client -Quiet -Count 1 -Delay 1) {
        $isOnline = "ONLINE"
    }
    ...
}

foreach ($client in $clients) {
    Start-Job -Name AdminPWReset -ScriptBlock $scriptBlock -ArgumentList $client
}

while (Get-Job -State "Running") {
    Start-Sleep -Milliseconds 100
}

Receive-Job -Name AdminPWReset | Out-File C:\test2.txt
Remove-Job -Name AdminPWReset

请注意,如果您有大量目标主机,您可能希望使用 job queue,这样您就不会同时处理数百个作业 运行。