Powershell Array -> HTML as string not as .Length - arrays

Currently I have a script that does a bunch of things, one thing is that it checks a .txt file to get all the current servers and then just does a ping to check connectivity before it does the rest of the script. I currently have that setup to add the servers it could not ping into an array so that it will not slow down the rest of the process attempting on failed servers. At the end of the script I have it all converted to an HTML document for ease of viewing. I would like to add the servers that failed the connection to the end of the HTML document to show that those servers failed.
As the script is now, it just prints the .Length property of the array I put those servers in. Here is the code I have that sets up the array, adds servers to it, and then the convertto-html part.
$servers = Get-Content "path\to\text.txt"
$failedConn = New-Object 'System.Collections.Generic.List[System.Object]'
foreach ($server in $servers)
{
$pingResult = Get-Ciminstance -ClassName win32_pingstatus -Filter "address='$server' and timeout=1000"
if ($pingResult.StatusCode -eq 0)
{
Write-Host "$server ping successful
}
else
{
Write-Host "$server ping Fail" -ForegroundColor Red
$failedConn.Add($server)
<# Also tried $failedConn += ($server) with the same error #>
}
}
<# Code for a forloop to do tasks where it adds an HTML variable called CombinedBody styling the tables whatnot #>
$CombinedBody += $failedConn | ConvertTo-Html | Out-File -Append C:\Path\To\html.html
The result just puts the failed connections at the very bottom of the HTML document, but it prints it as the length of each server name. How do I get it to print the actual server name? Any help is greatly appreciated!!

Length is the only property in the String object that ConvertTo-Html sees so that's what gets output. As a workaround, you can wrap the server names in another object that only have a single property containing the name, then it should output the actual names. Like this:
$failedConn | foreach{[pscustomobject]#{"Failed Servers" = $_}} | ConvertTo-Html | Out-File -Append C:\Path\To\html.html
Note that I've removed $CombinedBody += since Out-File doesn't return any output so that won't do anything.

Related

Possible to have different array names when write-output PSCustomObject]#{ in my function?

Is there any way to name the variable for an array something in my Catch { and then have another array name for the array inside Try/script part of my function?
Cause when i try doing like this $computerObject = [PSCustomObject]#{
and then doing Write-Output $computerArray i can only get either my variables inside Try/script array being displayed inside Powershell window. Or only get the $error message from my Catch.. Is there any way to name each array something so i can do like below.
write-host "Results"
Write-Output $computerArray - display my first array here
write-host "Failed: computerlist" -foregroundcolor red
Write-Output $computerArray2 - display $error computers here. $error should just include computers who did not answer to ping and other stuff from my invoke-command computerlist.txt
The only true answer to why i need this separately is that sometimes i want my array in a CSV file. And sometimes i just want to copy info directly from Powershell window. And then its more practical to have failed computers separated and not in the same array output.
This function (as mentioned in comments) doesn't leverage the CIM cmdlets parallel capabilities, would recommend some tweaks to it but to answer the actual question, how can you "split" the output between success and fail:
The function as-is, doesn't require any modification to achieve this, it's try and catch blocks are outputting objects with the same properties and luckily one of those properties is Error and it's value is a boolean so you can simply first query all the computers and then split the result using .Where with Split mode.
The code would be like this:
$computers = 'computer1', 'computer2', ....
$computerArray = foreach($computer in $computers) {
Get-ComputerInformation -ComputerName $computer
}
# now we can split between FAIL and SUCCESS
$fail, $success = $computerArray.Where({ $_.Error }, 'Split')
$success | Export-Csv path\to\success.csv -NoTypeInformation
$fail | Export-Csv path\to\fail.csv -NoTypeInformation

Powershell | Script that Analysis Used and Free Storage of Servers and than exports it to an CSV

i need to create an Script that checks the Free and Used storages of Hosts within our Network. The Hosts are loaded into an array and than those disks with Free Space and Size are being shown in the PowerShell.
Now i want that all of those things are being exported to an csv File so you can analyse the data easier.
Here is the script:
$servers = #("server1", "server2", "server3")
Foreach ($server in $servers)
{
$disks = Get-WmiObject Win32_LogicalDisk -ComputerName $server -Filter DriveType=3 |
Select-Object DeviceID,
#{'Name'='Size'; 'Expression'={[math]::truncate($_.size / 1GB)}},
#{'Name'='Freespace'; 'Expression'={[math]::truncate($_.freespace / 1GB)}}
$server
foreach ($disk in $disks)
{
$disk.DeviceID + $disk.FreeSpace.ToString("N0") + "GB / " + $disk.Size.ToString("N0") + "GB"
}
}
Thx btw :)
Instead of trying to control the output format by manually outputting the $server value, you'll want to "glue" the server name onto each associated $disk object, this will make it much easier to export to CSV (with the correct server name):
$servers = #("server1", "server2", "server3")
$allDisks = foreach ($server in $servers)
{
# Instead of assigning to `$disks` inside the loop,
# we let the output from all loop executions "bubble up"
# to the assignment to `$allDisks`
Get-WmiObject Win32_LogicalDisk -ComputerName $server -Filter DriveType=3 |
Select-Object #{'Name'='ComputerName'; 'Expression'={$server}},
DeviceID,
#{'Name'='Size'; 'Expression'={[math]::truncate($_.size / 1GB)}},
#{'Name'='Freespace'; 'Expression'={[math]::truncate($_.freespace / 1GB)}}
}
Now that each object has the correct server name attached, we can easily export to CSV:
$allDisks |Export-Csv .\path\to\output.csv -NoTypeInformation
... and we can also use PowerShell's native formatting subsystem for manipulating the output in the interactive shell nicely:
$allDisks |Format-Table DeviceID,Size,FreeSpace -GroupBy ComputerName

Test-Connection $False will not convert to ArrayList

Currently working on making a new report that will be generated with PowerShell. Using PowerShell to build a HTML email. I have one other report working fine but ran into an unexpected issue on this one.
The below code is just s sample from the script I am still building. Still adding pieces to the script but testing it as I move forward. I added a Test-Connection to see if a computer was responding or not and lost the ability to build an array.
My final goal with this report is to import a list of names from a file and then loop over all of the computers to see if they are pinging and gather some information from them using Get-WMIObject, etc.
The below code will replicate the issue I am having but I am not sure how to solve it. I've narrowed down the issue to when Test-Connection returns 'False'. On line 26 I am filtering for just results that returned a 'False' on Test-Connection to save them into its own array so that I can use that array in a different part of my code to build the HTML table/HTML to send out the email.
Only the flipside, if I tell it to look for only 'True', it will save into the array without issue.
This is the error that PowerShell is giving when doing filtering by 'False'.
Cannot convert value "#{Computer_Name=Computer1; Ping_Status=False}" to type "System.Collections.ArrayList". Error: "Cannot convert the "#{Computer_Name=Computer1 Ping_Status=False}" value of type "Selected.System.Management.Automation.PSCustomObject" to type "System.Collections.ArrayList"."
Please let me know if there is any other information that I can provide. I've been stuck on this one for a while. Co-workers are even say this is a weird one.
Is there something unique about the way Test-Connection return a 'False'?
CLS
[string]$ErrorActionPreference = "Continue"
[System.Collections.ArrayList]$Names = #(
"Computer1"
"Computer2"
)
[System.Collections.ArrayList]$WMI_Array = #()
[System.Collections.ArrayList]$Ping_Status_False = #()
foreach ($Name in $Names) {
[bool]$Ping_Status = Test-Connection $Name -Count 1 -Quiet
$WMI_Array_Object = [PSCustomObject]#{
'Computer_Name' = $Name
'Ping_Status' = $Ping_Status
}
$WMI_Array.Add($WMI_Array_Object) | Out-Null
}
$WMI_Array | Format-Table
[System.Collections.ArrayList]$Ping_Status_False = $WMI_Array | Where-Object {$_.Ping_Status -eq $false} | Select-Object Computer_Name, Ping_Status
$Ping_Status_False
The problem is not Test-Connection but that this statement
$WMI_Array | Where-Object {$_.Ping_Status -eq $false} | Select-Object Computer_Name, Ping_Status
produces just a single result. Which is not an array, and can thus not be converted to an ArrayList. The behavior is identical when you filter for $_.PingStatus -eq $true with just a single matching object, so I suspect that you had either more than one successfully pinged host or none at all when you tested that condition and it didn't throw the same error.
You could mitigate the problem by wrapping the statement in the array subexpression operator:
[Collections.ArrayList]$Ping_Status_False = #($WMI_Array |
Where-Object {$_.Ping_Status -eq $false} |
Select-Object Computer_Name, Ping_Status)
Or, you could simply drop all the pointless type-casting from your code:
$ErrorActionPreference = "Continue"
$Names = 'Computer1', 'Computer2'
$WMI_Array = foreach ($Name in $Names) {
[PSCustomObject]#{
'Computer_Name' = $Name
'Ping_Status' = [bool](Test-Connection $Name -Count 1 -Quiet)
}
}
$WMI_Array | Where-Object { -not $_.Ping_Status }

Invoke-WebRequest : Cannot bind parameter 'Uri'. Cannot convert the "#{

Would like to retrive innerText of a P html element for all the URLS i got listed in a text file. I'm rather newbie for this, but thought i can solve it. Atm. i've failed, as i can't handle how i shall pass each array items for the loop correctly:
$theURLS = Import-CSV linkek_alatt.txt
$item = New-Object System.Collections.ArrayList
for ($i=0; $i -le $theURLS.length;$i++)
{
foreach($item in $theURLS)
{
$Site = Invoke-WebRequest -URI $item
$Igotit = $Site.AllElements | Where-Object {$_.tagName -eq "P"} |
Select -First 1 -Skip 3 -ExpandProperty innerText
$Igotit
}
}
> FilteredContent.txt
The Filtered file shall contain the informations.
For now, i receive a "Invoke-WebRequest : Cannot bind parameter 'Uri'. Cannot convert the "#{" error and i see repeated first URL and a random - the one for next step - one in the error message. Any ideas welcome.
Best regards,
Geza
Per the comments, the issue occurs because you are using Import-CSV here:
$theURLS = Import-CSV linkek_alatt.txt
This attempts to create a PowerShell object by using the first line of text in the file as headers.
If your .txt file is not a .csv file and you want to return an array of strings, instead use Get-Content:
$theURLS = Get-Content linkek_alatt.txt
If your file is a CSV file, then you'll need to reference the property name that matches the header of the URLs when using it in Invoke-WebRequest. E.g if it had a header of "URL":
Invoke-WebRequest -URI $item.URL
But I suspect this is not the case for you.

Powershell - compare Active Directory usernames with e-mail address

I am currently trying to find all AD users that have been created using the model "firstname#domain.com" versus our new standard of "FirstInitialLastName#domain.com". I'm using the Quest ActiveRoles modules, specifically Get-QADUser to pull down my user details:
Get-QADUser -enabled -IncludedProperties PrimarySMTPAddress | ?{$_.Type -match "User"} | Select-Object FirstName,PrimarySMTPAddress ...
That gets me a list of user first names and their SMTP address. Where I am stumped is how to compare the results.
I thought normalizing the values (either adding "#domain.com" to the first name string or stripping "#domain.com" from the SMTP string) and then doing a -ieq test would be the best approach. I have found I can do the first with:
%{ $address=$($_.FirstName + "#domain.com";) }
But I can't figure out how to then test $address against the PrimarySMTPAddress string. I can create a second variable with:
%{ $smtp=$($_.PrimarySMTPAddress); }
and get the result:
[PS] C:\>$addy -ieq $smtp
True
I'm just unclear how to do it all in stream so that I can process my tree at once. If this is something that's just more suited to a script than a single line, that's fine too. Coming from the glorious world of BASH my brain just wanted to one-line it.
Get-QADUser -Enabled -Email * -SizeLimit 0 |
Where-Object {$_.Email.Split('#')[0] -eq $_.FirstName }
Try this:
Get-QADUser -enable -IncludedProperties PrimarySMTPAddress |
? { $_.PrimarySMTPAddress -match ("^"+[regex]::escape("$($_.firstname)")+"#domain.com") }

Resources