变量在循环期间未重置,造成误导性输出
Variable not resetting during loop creating misleading output
我这里有一个脚本可以读取桌面列表并更改这些桌面的管理员密码。它有效,但我有一个小问题。一旦错误出现在 Powershell 中,该值就永远不会重置。因此,例如第二个 DT 失败,第三个和第四个 DT 成功。在错误栏下,即使密码更改成功,它也会显示第二个 DT 的错误。但是,它在输出文件 (outputs.txt) 上运行良好。我试过 $error.clear() 但它没有解决我的问题。也许我把它放错地方了?如果您能帮我解决这个问题,我将不胜感激,因为我已完成 99.9%。谢谢。
[cmdletbinding()]
param (
[parameter(mandatory = $true)]
$InputFile,
$OutputDirectory
)
#Log failures in an output file called "outputs.txt" stored in the directory of input file
if(!$outputdirectory) {
$outputdirectory = (Get-Item $InputFile).directoryname
}
$failedcomputers = Join-Path $outputdirectory "outputs.txt"
$stream = [System.IO.StreamWriter] $failedcomputers
$stream.writeline("ComputerName `t IsOnline `t PasswordChangeStatus `t Error")
$stream.writeline("____________ `t ________ `t ____________________ `t _____")
#Prompt to confirm password twice and read password in a secure manner
$password = Read-Host "Enter the password" -AsSecureString
$confirmpassword = Read-Host "Confirm the password" -AsSecureString
#Convert password into plain text for comparison
$pwd1_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
$pwd2_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($confirmpassword))
#If the passwords don't match, script exits; otherwise, it continues.
if($pwd1_text -ne $pwd2_text) {
Write-Error "Entered passwords are not same. Script is exiting."
exit
}
#Check if input file exists. If file not found, script exits.
if(!(Test-Path $InputFile)) {
Write-Error "File ($InputFile) not found. Script is exiting."
exit
}
#Read contents of input file using Get-Content cmdlet and store list in an array called $computers
$Computers = Get-Content -Path $InputFile
#Loop through each computer account in the array and check if online or not using Test-Connection cmdlet
foreach ($Computer in $Computers) {
$Computer = $Computer.toupper()
$Isonline = "OFFLINE"
$Status = "SUCCESS"
Write-Verbose "Working on $Computer"
if((Test-Connection -ComputerName $Computer -count 1 -ErrorAction 0)) {
$Isonline = "ONLINE"
Write-Verbose "`t$Computer is Online"
} else { Write-Verbose "`t$Computer is OFFLINE" }
#If ping is successful, password is changed
try {
$account = [ADSI]("WinNT://$Computer/Administrator,user")
$account.psbase.invoke("setpassword",$pwd1_text)
Write-Verbose "`tPassword Change completed successfully"
}
#If password change fails, respective error is recorded
catch {
$status = "FAILED"
Write-Verbose "`tFailed to Change the administrator password. Error: $_"
$e = $_.Exception
$errmsg = $e.Message
while ($e.InnerException) {
$e = $e.InnerException
$errmsg = $e.Message
}
}
$obj = New-Object -TypeName PSObject -Property @{
ComputerName = $Computer
IsOnline = $Isonline
PasswordChangeStatus = $Status
Error = $errmsg
}
$obj | Select ComputerName, IsOnline, PasswordChangeStatus, Error
if($Status -eq "FAILED" -or $Isonline -eq "OFFLINE") {
$stream.writeline("$Computer `t`t $isonline `t`t $status `t $errmsg")
}else{
$stream.writeline("$Computer `t`t $isonline `t`t $status")
}
}
$stream.close()
Write-Host "`n`nFailed computers list is saved to $failedcomputers"
从你的小图片我可以看出,这条输出线引起了你的悲伤:
$obj | Select ComputerName, IsOnline, PasswordChangeStatus, Error
特别是 Error
属性,因为并不总是存在错误状态。简而言之,您需要解决变量 $errmsg
的内容,这是填充上述 属性 的内容。在循环的每次传递中,您都应该重置或清除变量。这个思路有几个选项。
在每次循环开始时将变量设置为空字符串
foreach ($Computer in $Computers) {
$Computer = $Computer.toupper()
$Isonline = "OFFLINE"
$Status = "SUCCESS"
$errmsg = ""
.... more code stuff...
您还可以在 try 块之前或输出行之后的类似位置使用 cmdlet Clear-Variable。 注意 $
是故意省略的。
Clear-Variable errmsg
我完全支持马特的回答!这两个选项中的任何一个都是解决您问题的简单方法。我提供的更多是重写,对某些事情的完成方式进行了一些明确的更改。我放弃了流编写器以支持或收集结果,并在最后使用 Set-Content 将它们转储到一个文件中(为什么要为整个过程锁定打开一个文件并向其中写入一点点,而您可以一次完成所有操作结束?)。此外,我在每个循环的开始创建您的计算机对象,并一起跳过 $IsOnline
、$Status
和 $errmsg
变量。然后对于每个循环,我将该对象添加到一个数组(在循环之前创建),并在最后输出它。
[cmdletbinding()]
param (
[parameter(mandatory = $true)]
$InputFile,
$OutputDirectory
)
#Log failures in an output file called "failed-computers.txt" stored in the directory of input file
if(!$outputdirectory) {
$outputdirectory = (Get-Item $InputFile).directoryname
}
$failedcomputers = Join-Path $outputdirectory "failed-computers.txt"
#Prompt to confirm password twice and read password in a secure manner
$password = Read-Host "Enter the password" -AsSecureString
$confirmpassword = Read-Host "Confirm the password" -AsSecureString
#Convert password into plain text for comparison
$pwd1_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
$pwd2_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($confirmpassword))
#If the passwords don't match, script exits; otherwise, it continues.
if($pwd1_text -ne $pwd2_text) {
Write-Error "Entered passwords are not same. Script is exiting."
exit
}
#Check if input file exists. If file not found, script exits.
if(!(Test-Path $InputFile)) {
Write-Error "File ($InputFile) not found. Script is exiting."
exit
}
#Read contents of input file using Get-Content cmdlet and store list in an array called $computers
$Computers = Get-Content -Path $InputFile
#Create an empty array to populate with the results of the password updates
$Results = @()
#Loop through each computer account in the array and check if online or not using Test-Connection cmdlet
foreach ($ComputerName in $Computers) {
#Create Computer object
$Computer = New-Object PSObject -Prop @{
'Name' = $ComputerName.toupper()
'Isonline' = "OFFLINE"
'Status' = "SUCCESS"
'Error' = ""
}
Write-Verbose "Working on $ComputerName"
if((Test-Connection -ComputerName $Computer.Name -count 1 -ErrorAction 0)) {
$Computer.Isonline = "ONLINE"
Write-Verbose "`t$ComputerName is Online"
} else { Write-Verbose "`t$ComputerName is OFFLINE" }
#If ping is successful, password is changed
try {
$account = [ADSI]("WinNT://$ComputerName/Administrator,user")
$account.psbase.invoke("setpassword",$pwd1_text)
Write-Verbose "`tPassword Change completed successfully"
}
#If password change fails, respective error is recorded
catch {
$Computer.status = "FAILED"
Write-Verbose "`tFailed to Change the administrator password. Error: $_"
$e = $_.Exception
$Computer.Error = $e.Message
while ($e.InnerException) {
$e = $e.InnerException
$Computer.Error = $e.Message
}
}
#Display resutls to screen
$Computer
#Add computer's results/errors to results array
$Results += $Computer
}
#Write results to file
$Results|Format-Table -AutoSize|Out-String|Set-Content $failedcomputers
$Results|Export-Csv ($failedcomputers -replace "txt$","csv") -NoTypeInformation
Write-Host "`n`nFailed computers list is saved to $failedcomputers, with CSV export of the same name and location"
哦,是的,我还将整个内容输出为 CSV,以便可以在 Excel 中轻松打开它,以防您以后需要使用它(过滤更新失败、离线的系统,或其他)。
我这里有一个脚本可以读取桌面列表并更改这些桌面的管理员密码。它有效,但我有一个小问题。一旦错误出现在 Powershell 中,该值就永远不会重置。因此,例如第二个 DT 失败,第三个和第四个 DT 成功。在错误栏下,即使密码更改成功,它也会显示第二个 DT 的错误。但是,它在输出文件 (outputs.txt) 上运行良好。我试过 $error.clear() 但它没有解决我的问题。也许我把它放错地方了?如果您能帮我解决这个问题,我将不胜感激,因为我已完成 99.9%。谢谢。
[cmdletbinding()]
param (
[parameter(mandatory = $true)]
$InputFile,
$OutputDirectory
)
#Log failures in an output file called "outputs.txt" stored in the directory of input file
if(!$outputdirectory) {
$outputdirectory = (Get-Item $InputFile).directoryname
}
$failedcomputers = Join-Path $outputdirectory "outputs.txt"
$stream = [System.IO.StreamWriter] $failedcomputers
$stream.writeline("ComputerName `t IsOnline `t PasswordChangeStatus `t Error")
$stream.writeline("____________ `t ________ `t ____________________ `t _____")
#Prompt to confirm password twice and read password in a secure manner
$password = Read-Host "Enter the password" -AsSecureString
$confirmpassword = Read-Host "Confirm the password" -AsSecureString
#Convert password into plain text for comparison
$pwd1_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
$pwd2_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($confirmpassword))
#If the passwords don't match, script exits; otherwise, it continues.
if($pwd1_text -ne $pwd2_text) {
Write-Error "Entered passwords are not same. Script is exiting."
exit
}
#Check if input file exists. If file not found, script exits.
if(!(Test-Path $InputFile)) {
Write-Error "File ($InputFile) not found. Script is exiting."
exit
}
#Read contents of input file using Get-Content cmdlet and store list in an array called $computers
$Computers = Get-Content -Path $InputFile
#Loop through each computer account in the array and check if online or not using Test-Connection cmdlet
foreach ($Computer in $Computers) {
$Computer = $Computer.toupper()
$Isonline = "OFFLINE"
$Status = "SUCCESS"
Write-Verbose "Working on $Computer"
if((Test-Connection -ComputerName $Computer -count 1 -ErrorAction 0)) {
$Isonline = "ONLINE"
Write-Verbose "`t$Computer is Online"
} else { Write-Verbose "`t$Computer is OFFLINE" }
#If ping is successful, password is changed
try {
$account = [ADSI]("WinNT://$Computer/Administrator,user")
$account.psbase.invoke("setpassword",$pwd1_text)
Write-Verbose "`tPassword Change completed successfully"
}
#If password change fails, respective error is recorded
catch {
$status = "FAILED"
Write-Verbose "`tFailed to Change the administrator password. Error: $_"
$e = $_.Exception
$errmsg = $e.Message
while ($e.InnerException) {
$e = $e.InnerException
$errmsg = $e.Message
}
}
$obj = New-Object -TypeName PSObject -Property @{
ComputerName = $Computer
IsOnline = $Isonline
PasswordChangeStatus = $Status
Error = $errmsg
}
$obj | Select ComputerName, IsOnline, PasswordChangeStatus, Error
if($Status -eq "FAILED" -or $Isonline -eq "OFFLINE") {
$stream.writeline("$Computer `t`t $isonline `t`t $status `t $errmsg")
}else{
$stream.writeline("$Computer `t`t $isonline `t`t $status")
}
}
$stream.close()
Write-Host "`n`nFailed computers list is saved to $failedcomputers"
从你的小图片我可以看出,这条输出线引起了你的悲伤:
$obj | Select ComputerName, IsOnline, PasswordChangeStatus, Error
特别是 Error
属性,因为并不总是存在错误状态。简而言之,您需要解决变量 $errmsg
的内容,这是填充上述 属性 的内容。在循环的每次传递中,您都应该重置或清除变量。这个思路有几个选项。
在每次循环开始时将变量设置为空字符串
foreach ($Computer in $Computers) { $Computer = $Computer.toupper() $Isonline = "OFFLINE" $Status = "SUCCESS" $errmsg = "" .... more code stuff...
您还可以在 try 块之前或输出行之后的类似位置使用 cmdlet Clear-Variable。 注意
$
是故意省略的。Clear-Variable errmsg
我完全支持马特的回答!这两个选项中的任何一个都是解决您问题的简单方法。我提供的更多是重写,对某些事情的完成方式进行了一些明确的更改。我放弃了流编写器以支持或收集结果,并在最后使用 Set-Content 将它们转储到一个文件中(为什么要为整个过程锁定打开一个文件并向其中写入一点点,而您可以一次完成所有操作结束?)。此外,我在每个循环的开始创建您的计算机对象,并一起跳过 $IsOnline
、$Status
和 $errmsg
变量。然后对于每个循环,我将该对象添加到一个数组(在循环之前创建),并在最后输出它。
[cmdletbinding()]
param (
[parameter(mandatory = $true)]
$InputFile,
$OutputDirectory
)
#Log failures in an output file called "failed-computers.txt" stored in the directory of input file
if(!$outputdirectory) {
$outputdirectory = (Get-Item $InputFile).directoryname
}
$failedcomputers = Join-Path $outputdirectory "failed-computers.txt"
#Prompt to confirm password twice and read password in a secure manner
$password = Read-Host "Enter the password" -AsSecureString
$confirmpassword = Read-Host "Confirm the password" -AsSecureString
#Convert password into plain text for comparison
$pwd1_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
$pwd2_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($confirmpassword))
#If the passwords don't match, script exits; otherwise, it continues.
if($pwd1_text -ne $pwd2_text) {
Write-Error "Entered passwords are not same. Script is exiting."
exit
}
#Check if input file exists. If file not found, script exits.
if(!(Test-Path $InputFile)) {
Write-Error "File ($InputFile) not found. Script is exiting."
exit
}
#Read contents of input file using Get-Content cmdlet and store list in an array called $computers
$Computers = Get-Content -Path $InputFile
#Create an empty array to populate with the results of the password updates
$Results = @()
#Loop through each computer account in the array and check if online or not using Test-Connection cmdlet
foreach ($ComputerName in $Computers) {
#Create Computer object
$Computer = New-Object PSObject -Prop @{
'Name' = $ComputerName.toupper()
'Isonline' = "OFFLINE"
'Status' = "SUCCESS"
'Error' = ""
}
Write-Verbose "Working on $ComputerName"
if((Test-Connection -ComputerName $Computer.Name -count 1 -ErrorAction 0)) {
$Computer.Isonline = "ONLINE"
Write-Verbose "`t$ComputerName is Online"
} else { Write-Verbose "`t$ComputerName is OFFLINE" }
#If ping is successful, password is changed
try {
$account = [ADSI]("WinNT://$ComputerName/Administrator,user")
$account.psbase.invoke("setpassword",$pwd1_text)
Write-Verbose "`tPassword Change completed successfully"
}
#If password change fails, respective error is recorded
catch {
$Computer.status = "FAILED"
Write-Verbose "`tFailed to Change the administrator password. Error: $_"
$e = $_.Exception
$Computer.Error = $e.Message
while ($e.InnerException) {
$e = $e.InnerException
$Computer.Error = $e.Message
}
}
#Display resutls to screen
$Computer
#Add computer's results/errors to results array
$Results += $Computer
}
#Write results to file
$Results|Format-Table -AutoSize|Out-String|Set-Content $failedcomputers
$Results|Export-Csv ($failedcomputers -replace "txt$","csv") -NoTypeInformation
Write-Host "`n`nFailed computers list is saved to $failedcomputers, with CSV export of the same name and location"
哦,是的,我还将整个内容输出为 CSV,以便可以在 Excel 中轻松打开它,以防您以后需要使用它(过滤更新失败、离线的系统,或其他)。