【问题标题】:Getting output from background jobs从后台作业获取输出
【发布时间】:2016-01-09 05:18:55
【问题描述】:

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

# 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

【问题讨论】:

  • 没有行号,能不能加个cmets去代码显示你说的是哪几行?
  • $stream 在哪里定义?如果未定义,则无法写入。
  • @Eris 我在受影响的区域添加了这样的 ------------------ 行。另外,您对流的看法是正确的。我会解决这个问题,但我真正关心的第一个脚本中的信息是 - $obj |选择计算机名、在线、changeStatus | Out-File -FilePath C:\test.txt -Append

标签: powershell jobs powershell-ise


【解决方案1】:

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

$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,这样您就不会有数百个作业并行运行。

【讨论】:

  • 我试过了,现在工作正常了,但是重置密码的实际操作失败了,我似乎无法弄清楚原因。
  • @cmorris14 可能是因为您在脚本块内使用了脚本块外定义的其他变量(例如$adminuser)。您还应该优化有关脚本块的其他内容(例如,连接测试应该在外面,以防止尝试在最初不可用的计算机上启动作业。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多