0

Good-day folks,

I am attempting to adapt this script (originally authored by Sitaram Pamarthi), for the primary purpose of automating a mundane administrative task of managing local admin passwords on the Windows systems I have under my control. So far the script is working - in that I am able to change the password and get the email output (albeit in text format) - but it's not quite there yet.

This is an excerpt from my script:

Complete script is available here for full context.

$Computers = Get-Content -Path $InputFile

#Now let's loop through each computer and change the specified account password.
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" }

    Try {
        $Account = [ADSI]("WinNT://$Computer/$TargetAccount,user")
        $Account.psbase.invoke("setpassword",$Pwd1_Text)
        Write-Verbose "`tPassword Change completed successfully"
    }
    Catch {
        $Status = "FAILED"
        Write-Verbose "`tFailed to Change the password for $TargetAccount on $Computer. Error: $_"
        $StatsError = "$_"
    }

    $Obj = New-Object -TypeName PSObject -Property @{
        Date = "$(Get-Date)"
        ComputerName = $Computer
        IsOnline = $IsOnline
        PasswordChangeStatus = $Status
        DetailedStatus = $StatsError
    }

    $Obj | Format-Table ComputerName, Date, IsOnline, PasswordChangeStatus, DetailedStatus -AutoSize
    $Obj | Select-Object "ComputerName", "Date", "IsOnline", "PasswordChangeStatus", "DetailedStatus" | Export-Csv -Append -Path ".\output.csv" -NoTypeInformation

    If($Status -eq "FAILED" -or $IsOnline -eq "OFFLINE") {
        $Stream.Writeline("$Computer `t $IsOnline `t $Status")
    }
}

#Finally, let's close the Stream Writer, write the stream to the text file
#and notify the user of the location of the FailedComputers log file
$Stream.Close()
Write-Host "`n`nFailed computers list is saved to $FailedComputers"
$ScriptEndDate = $(Get-Date)

#Send an email log of what was done
$MsgBody = @"

Dear IT Admins,

Please be advised that the local admin account password for [ $TargetAccount ] has been changed
successfully on the following hosts:

Action Initiated by:  `t$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
Script Started:  `t$ScriptStartDate
Script Ended:  `t$ScriptEndDate

"@
Send-MailMessage -Body $MsgBody -Verbose

# Finally, let's clear the default parameters we set earlier
$PSDefaultParameterValues.Clear()

#END SCRIPT

The script currently outputs to the PowerShell console as follows:

ComputerName Date                IsOnline PasswordChangeStatus DetailedStatus
------------ ----                -------- -------------------- --------------
HOME2        02/10/2019 06:18:20 ONLINE   FAILED               Exception calling "Invoke" with "2" argument(s): "Access is denied....



ComputerName Date                IsOnline PasswordChangeStatus DetailedStatus
------------ ----                -------- -------------------- --------------
HOME3        02/10/2019 06:18:22 ONLINE   FAILED               Exception calling "Invoke" with "2" argument(s): "Access is denied....



ComputerName Date                IsOnline PasswordChangeStatus DetailedStatus
------------ ----                -------- -------------------- --------------
HOME4        02/10/2019 06:18:29 OFFLINE  FAILED               Exception calling "Invoke" with "2" argument(s): "The network path was not found....

SO MY QUESTION - How can I get my script to consolidate the output as follows (with a single header row for both ONLINE and OFFLINE hosts) so that I can include it in the email body?

ComputerName Date                IsOnline PasswordChangeStatus DetailedStatus
------------ ----                -------- -------------------- --------------
HOME2        02/10/2019 06:18:20 ONLINE   FAILED               Exception calling "Invoke" with "2" argument(s): "Access is denied....
HOME3        02/10/2019 06:18:22 ONLINE   FAILED               Exception calling "Invoke" with "2" argument(s): "Access is denied....
HOME4        02/10/2019 06:18:29 OFFLINE  FAILED               Exception calling "Invoke" with "2" argument(s): "The network path was not found....

I believe that I may have to convert the console output into a table somehow and then include CSS in a header variable that can be passed to the Send-MailMessage cmdlet in order to apply some styling to the email message. But I have yet to figure out how to accomplish this, so any help from this platform would be very much appreciated.

Thank you all!

5
  • 2
    a) If you have 2 questions: please post 2 questions. b) Please reduce your wall of code to a minimal reproducible example. Commented Feb 10, 2019 at 12:46
  • Thanks for the comments. I thought since the questions were related, I would include them in the same post. However, if this is unacceptable then I don't mind editing the question. With regards to the "wall of code", I thought it better to provide the entire script for proper context - no? Commented Feb 10, 2019 at 12:56
  • 1
    Even if both questions are related in that they're both steps on the way from your input to sending the email, they're different in what they entail. And while providing context is appreciated, posting an entire script usually hurts more than it helps, because there usually is too much stuff in it that distracts from the actual problem. That's why the guidelines ask you to create a minimal reproducible example (which includes test-running that code to make sure it still exposes the problem you're trying to debug). Commented Feb 10, 2019 at 13:05
  • Hint for the 1st question: move the printing logic out of the foreach loop. Commented Feb 10, 2019 at 14:38
  • Thanks for the comments, I'll update my post accordingly. Also, thanks for the hint @vonPryz Commented Feb 10, 2019 at 16:30

1 Answer 1

1

A bit late to the game, but I would say that you should collect / store all your PSObjects in an ArrayList, and then apply your Format-Table. This example works as expected - I'm simply testing if the computer is online and commented your change password code.

$Computers = @('SERVERA', 'SERVERB')

[System.Collections.ArrayList] $res = New-Object -TypeName "System.Collections.ArrayList"

#Now let's loop through each computer and change the specified account password.
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" }

    <#
    Try {
        $Account = [ADSI]("WinNT://$Computer/$TargetAccount,user")
        $Account.psbase.invoke("setpassword",$Pwd1_Text)
        Write-Verbose "`tPassword Change completed successfully"
    }
    Catch {
        $Status = "FAILED"
        Write-Verbose "`tFailed to Change the password for $TargetAccount on $Computer. Error: $_"
        $StatsError = "$_"
    }
    #>

    $Obj = New-Object -TypeName PSObject -Property @{
        Date = "$(Get-Date)"
        ComputerName = $Computer
        IsOnline = $IsOnline
        PasswordChangeStatus = $Status
        DetailedStatus = $StatsError
    }

    $res.Add($Obj)

    #$Obj | Format-Table ComputerName, Date, IsOnline, PasswordChangeStatus, DetailedStatus -AutoSize
    #$Obj | Select-Object "ComputerName", "Date", "IsOnline", "PasswordChangeStatus", "DetailedStatus" | Export-Csv -Append -Path ".\output.csv" -NoTypeInformation

    If($Status -eq "FAILED" -or $IsOnline -eq "OFFLINE") {
        $Stream.Writeline("$Computer `t $IsOnline `t $Status")
    }
}

$res.ToArray() | Format-Table ComputerName, Date, IsOnline, PasswordChangeStatus, DetailedStatus -AutoSize

#This will output 1 file, with all the computers in it, with only 1 set of headers.
$res.ToArray() | Select-Object "ComputerName", "Date", "IsOnline", "PasswordChangeStatus", "DetailedStatus" | Export-Csv -Append -Path ".\output.csv" -NoTypeInformation

It produces a result like this

ComputerName Date                IsOnline PasswordChangeStatus DetailedStatus
------------ ----                -------- -------------------- --------------
SERVERA      02/11/2019 11:05:57 ONLINE   SUCCESS
SERVERB      02/11/2019 11:05:57 ONLINE   SUCCESS

I guessing that because you keep executing the Format-Table cmdlet, the console doesn't know that you want to output the same object type. So because you are calling it multiple times, is has to make sure that the output table is being explicit parsed to the console. While when you have things in an array and then only call the Format-Table once - you get the expected result as shown.

Please note that I also moved the Export-Csv out of your foreach and that also works like expected.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you very much, this absolutely worked for me. I had an idea that I needed to use an ArrayList, but just couldn't figure out how. Looks like what I was missing was the .ToArray() bit. Thanks again - now on to figuring out house to include the CSS styling when I ConvertTo-HTML so I can email it out. Thanks again.
Create a new question and let us have a look at it :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.