Powershell 从后台作业获取输出

Powershell 从后台作业获取输出,powershell,jobs,powershell-ise,Powershell,Jobs,Powershell Ise,我创建了以下脚本来重置特定主机文件中所有计算机上本地管理员帐户的密码。这个脚本可以正常工作并提供有用的输出,但速度很慢,因为它一次只能处理一台机器 # function to convert a secure string to a standard string function ConvertTo-String { param( [System.Security.SecureString] $secureString ) $marshal = [Sy

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

# 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中:

$scriptBlock = {
    foreach ($client in $clients) {
        ...
    }
}
但是在scriptblock外部初始化了客户端列表(
$clients
),因此scriptblock内部的变量
$clients
是不同的(空)变量,因为它在不同的范围内。因此,您的作业在一个空列表上迭代,因此不会产生任何输出

为了能够在scriptblock中使用客户机列表,您必须使用
使用:
范围修饰符:

$scriptBlock = {
    foreach ($client in $using:clients) {
        ...
    }
}
或将客户端列表作为参数传递到scriptblock中:

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

Start-Job -ScriptBlock $scriptBlock -ArgumentList $clients
正如我从您的代码中看到的,您正在尝试执行后者:

Start-Job $scriptBlock -ArgumentList $_ -Name AdminPWReset
但是,由于两个原因,这不起作用:

  • 在运行
    Start Job
    的上下文中,没有当前对象,因此
    $\u
    为空,不会向脚本块传递任何内容
  • scriptblock既没有
    Param()
    块,也没有使用自动变量
    $args
    ,因此即使将参数传递给scriptblock,也不会使用它
话虽如此,您首先不希望传递
$clients
,因为即使它有效,也不会加快任何速度,因为整个客户端列表仍将按顺序处理。实际上,您要做的是并行处理列表。为此,必须仅将测试放入scriptblock,然后为循环中的每个客户端启动一个作业:

$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

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

没有行号,您是否可以添加注释到代码中以显示您正在谈论的行?哪里定义了
$stream
?如果它没有定义,您就不能写入它。@Eris我在受影响的区域中添加了这样的行-------------------。另外,你对这条小溪的看法是对的。我会解决这个问题,但我真正关心的第一个脚本中的信息是-$obj |选择computerName,Online,changeStatus | Out File-FilePath C:\test.txt-AppendI尝试了这个方法,现在工作正常了,但是重置密码的实际操作失败了,我似乎无法解释原因。@cmorris14可能是因为您在scriptblock内部使用了在scriptblock外部定义的其他变量(
$adminuser
)。关于scriptblock,您还应该优化其他方面(例如,连接测试应该在外部进行,以防止尝试在一开始不可用的计算机上启动作业)。