2016-01-08 2 views
1

Я создал следующий скрипт для сброса пароля локальной учетной записи администратора на всех компьютерах в конкретном файле хоста. Этот скрипт функционирует должным образом и обеспечивает полезный вывод, но он медленный, поскольку он работает только по одной машине за раз.Получение выходных данных из фоновых заданий

# 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 
+0

Нет номеров строк, не могли бы вы добавить комментарии, чтобы перейти к коду, чтобы показать, какие строки вы говорите? – Eris

+0

Где задан '$ stream'? Если он не определен, вы не можете писать на него. – Eris

+0

@Eris Я добавил строки, подобные этому ------------------ в затронутых областях. Кроме того, вы правы в отношении потока. Я исправлю это, но информация из первого скрипта, который мне действительно волнует, - $ obj | Выберите computerName, Online, changeStatus | Out-File -FilePath C: \ test.txt -Append – cmorris14

ответ

1

Вы не получите выход, потому что вы переместили весь цикл в ScriptBlock:

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

но инициализируется свой список клиентов ($clients) вне в ScriptBlock, так что переменная $clientsвнутри ScriptBlock является другой (пустой) переменной, так как она находится в другой области. Из-за этого ваша работа выполняет итерацию по пустым спискам и, следовательно, не производит никакого вывода.

Чтобы иметь возможность использовать список клиентов внутри ScriptBlock вы должны использовать using: области действия модификатора:

$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 нет текущего объекта, так $_ пусто и ничего не передаются в ScriptBlock.
  • В блоке Scriptblock нет блока Param(), и он не использует автоматическую переменную $args, поэтому, даже если вы передали аргумент в скриптблокс, он никогда не будет использоваться.

С учетом сказанного, вы не хотите, чтобы пройти $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 

Обратите внимание, что если у вас есть большое количество целевых хостов вы можете захотеть использовать job queue, так что у вас нет сотен заданий, работающих параллельно.

+0

Я пробовал это, и теперь работа работает, но фактическое действие сброса пароля не удается, и я не могу понять, почему. – cmorris14

+1

@ cmorris14 Возможно, потому, что вы используете другие переменные внутри скриптаблока, которые определены вне скриптового блока (например, '$ adminuser). Есть и другие вещи о скриптблоке, который вы также должны оптимизировать (например, тест подключения должен быть снаружи, чтобы предотвратить попытки запуска заданий на компьютерах, которые не доступны в первую очередь. –

+0

Спасибо, заработал. – cmorris14

 Смежные вопросы

  • Нет связанных вопросов^_^