ConvertTo-HTML outputs System.Object[] from PSObject - html

I try to convert a PSObject to a HTML table and get System.Object[] as output.
$Result = New-Object PSObject
foreach ($Location in $Locations) {
$Servers = GetServers -Location $Location
$Value = #()
foreach ($Server in $Servers) {
if (Test-Path Path) {
$value += $Server #Background of td should be green
} else {
$Value += $Server #Background of td should be red
}
}
$Result | Add-Member -Type NoteProperty -Name $Location -Value $Value
}
$Result ConvertTo-Html
Without converting to HTML the output is:
Location1 : {Server1, Server2}
Location2 : {Server3, Server4}
Location3 : {Server5, Server6}
Trying to convert:
<td>System.Object[]</td>
And I'd like to have a table like:
Location1 Location2 Location3
--------- --------- ---------
Server1 Server3 Server5
Server2 Server4 Server6
If it's possible I would like to have different <td> background (as commented in the script).

ConvertTo-Html doesn't know how to format string arrays, so you'll have to convert the value to a string before passing it to the cmdlet:
$Result | Add-Member -Type NoteProperty -Name $Location -Value ($Value -join ', ')

Related

How to pass variable value to Invoke-Command

I am trying to get the list of machines which are in particular state (Saved, Running, Stopped). I am passing the state of the machine as an argument in a function.
Function global:Resource-Summary
{
Param(
[parameter(mandatory=$true)] $ProgramName,
[parameter(mandatory=$true)] $ServerName
)
PROCESS
{
Foreach ($Server in $ServerName)
{
Invoke-Command -ComputerName $Server -ScriptBlock {
$VMs = Get-VM
$colVMs = #()
foreach ($VM in $VMs)
{
$objVM = New-Object System.Object
$objVM | Add-Member -MemberType NoteProperty -Name VMName -Value $VM.VMName
$objVM | Add-Member -MemberType NoteProperty -Name VMNotes -Value $VM.Notes
$objVM | Add-Member -MemberType NoteProperty -Name VMState -Value $VM.State
$colVMs += $objVM
}
$a = #{Expression={$_.VMName};Label='VM Name'}, `
#{Expression={$_.VMNotes};Label='VM Description'}, `
#{Expression={$_.VMState};Label='State'}
"Program Name : $ProgramName"
$colVMs |Where-Object {($_.VMState -eq '$ProgramName')} | Format-Table $a -AutoSize
} -ArgumentList $ProgramName
}
}
}
When I run Resource-Summary -ProgramName Running -ServerName Demo
I do not get any value.
When I replace $ProgramName with RUNNING I get the expected output.
For reference, see e.g. this post on how Pass arguments to a scriptblock in powershell
.
The problem in your script is how you call the script block, this is explained in more detail in the link above, but you need to pass any "external" input to it the same way as if you'd call it like a function.
You are doing this partially correctly, you are using the -ArgumentList parameter to send $ProgramName to the scriptslock but you haven't specified in the scriptblock how to access it.
For example, check out
Invoke-Command -ArgumentList "Application" -ScriptBlock {
param($log)
Get-EventLog $log
}
Here -ArgumentList contains the input, and inside the scriptblock, $log is assigned its value.
Updating your script to take that into account:
Function global:Resource-Summary
{
Param(
[parameter(mandatory=$true)] $ProgramName,
[parameter(mandatory=$true)] $ServerName
)
PROCESS
{
Foreach ($Server in $ServerName)
{
Invoke-Command -ComputerName $Server -ScriptBlock {
param($name)
$VMs = Get-VM
$colVMs = #()
foreach ($VM in $VMs)
{
$objVM = New-Object System.Object
$objVM | Add-Member -MemberType NoteProperty -Name VMName -Value $VM.VMName
$objVM | Add-Member -MemberType NoteProperty -Name VMNotes -Value $VM.Notes
$objVM | Add-Member -MemberType NoteProperty -Name VMState -Value $VM.State
$colVMs += $objVM
}
$a = #{Expression={$_.VMName};Label='VM Name'}, `
#{Expression={$_.VMNotes};Label='VM Description'}, `
#{Expression={$_.VMState};Label='State'}
"Program Name : $ProgramName"
$colVMs |Where-Object {($_.VMState -eq "$name") | Format-Table $a -AutoSize
}
} -ArgumentList $ProgramName
}
}
}
Also, in my opinion, the -ArgumentList is on the wrong line, it needs to be after the following closing bracket, one line done. This way it's on the same cmdlet as Invoke-Command and the scriptblock.
A couple of things jump out:
If you have a variable inside single quotes, it won't be automatically resolved by PowerShell, so in this section, you are actually comparing the state of the VM to the string $ProgramName and not it's value:
$_.VMState -eq '$ProgramName'
Try changing to double quotes, or none at all.
Also, check the bracketing - you're missing the closing one for Where-Object.

Add nested properties to a PowerShell object

I'm working with a script in PowerShell that updates JSON data, accessing an modifying data that's 3 layers deep. The general flow is:
$obj = Get-Content -Raw -Path $pathstring | ConvertFrom-Json
$obj.prop1.prop2.prop3.prop4 = "test"
$outjson = ConvertTo-Json -InputObject $obj -Depth 5
Set-Content -Path $pathstring -Value $outjson
This works when the property already exists. However, in some cases $obj.prop1.prop2.prop3.prop4 does not exist. I want to add a series of nested properties to a PowerShell object, and then convert that to JSON to create it.
Is that possible/how is that done/is there a better way to add JSON values to something in PowerShell?
Edit: I'm currently running
if(Get-Member -inputobject $js.prop1 -name "prop2" -Membertype Properties)
to test if the property exists, and if prop2 doesn't exist then I need to create all the properties.
#J.Peter I really liked your solution, but I needed to be able to provide a way to add properties in the name with periods. so I made a slight mod to yours. I added an escape character to the parameters, that gets replaced in the string with a period.
Edit: another rewrite. Got rid of the recursion, and now it can handle creating very complex objects as well as having "." in the property names. Was so happy with the changes I made a gist for it https://gist.github.com/tcartwright/72cac052e1f8058abed1f7028f674a10 with credits.
function Add-NoteProperty {
param(
$InputObject,
$Property,
$Value,
[switch]$Force,
[char]$escapeChar = '#'
)
process {
$path = $Property -split "\."
$obj = $InputObject
# loop all but the very last property
for ($x = 0; $x -lt $path.count -1; $x ++) {
$propName = $path[$x] -replace $escapeChar, '.'
if (!($obj | Get-Member -MemberType NoteProperty -Name $propName)) {
$obj | Add-Member NoteProperty -Name $propName -Value (New-Object PSCustomObject) -Force:$Force.IsPresent
}
$obj = $obj.$propName
}
$propName = ($path | Select-Object -Last 1) -replace $escapeChar, '.'
if (!($obj | Get-Member -MemberType NoteProperty -Name $propName)) {
$obj | Add-Member NoteProperty -Name $propName -Value $Value -Force:$Force.IsPresent
}
}
}
$obj = [PSCustomObject]#{}
Add-NoteProperty -InputObject $obj -Property "Person.Name.First" -Value "Tim"
Add-NoteProperty -InputObject $obj -Property "Person.Name.Last" -Value "C"
Add-NoteProperty -InputObject $obj -Property "Person.Age" -Value "Old"
Add-NoteProperty -InputObject $obj -Property "Person.Address.City" -Value "Houston"
Add-NoteProperty -InputObject $obj -Property "Person.Address.State" -Value "Texas"
$obj | ConvertTo-JSON
Which results in:
{
"Person": {
"Name": {
"First": "Tim",
"Last": "C"
},
"Age": "Old",
"Address": {
"City": "Houston",
"State": "Texas"
}
}
}
If a property doesn't exist you need to add it, otherwise you can't assign a value to it:
$obj.prop1.prop2.prop3 | Add-Member -Type NoteProperty -Name 'prop4' -Value 'test'
I recently encountered a similiar problem where I needed to add nested properties to objects so I wrote a recursive function for it.
function Add-NoteProperty {
param(
$InputObject,
$Property,
$Value,
[switch]$Force
)
process {
[array]$path = $Property -split "\."
If ($Path.Count -gt 1) {
#go in to recursive mode
$Obj = New-Object PSCustomObject
Add-NoteProperty -InputObject $Obj -Property ($path[1..($path.count - 1)] -join ".") -Value $Value
}
else {
#last node
$Obj = $Value
}
$InputObject | Add-Member NoteProperty -Name $path[0] -Value $Obj -Force:$Force
}
}
Usage example:
$obj = [PSCustomObject]#{
prop1 = "1"
prop2 = "2"
}
Add-NoteProperty -InputObject $obj -Property "prop3.nestedprop31.nestedprop311" -Value "somevalue"
$obj | ConvertTo-JSON
<#Should give you this
{
"prop1": "1",
"prop2": "2",
"prop3": {
"subprop": {
"asdf": "3"
}
}
}
#>

Powershell: Get-Content from the large file (server list) [duplicate]

This question already has answers here:
Can Powershell Run Commands in Parallel?
(10 answers)
Closed 6 years ago.
I have 100,000 list of servers from the text file (serverlist.txt)
When I run in one shot it will burst my memory and cpu and the time took longer (about 3 days)to complete the scanning for DNSlookup.
I tried to split the file that contain 20k list of servers below and can be completed to scan up to 10mins for each file.
serverlist1.txt
serverlist2.txt
serverlist3.txt
serverlist4.txt
serverlist5.txt
$objContainer = #()
$values = #()
$domains = Get-Content -path "serverlist1.txt"
$named = 0
$timestamp= get-date
$domains | ForEach-Object {
$domain = $_
nslookup $domain 2>&1 | ForEach-Object {
if ($_ -match '^Name:\s*(.*)$') {
$values += $matches[1]
$named = 1;
} elseif (($_ -match '^.*?(\d*\.\d*\.\d*\.\d*)$') -and ($named -eq 1)) {
$values += $matches[1]
} elseif ($_ -match '^Aliases:\s*(.*)$') {
$values += $matches[1]
}
}
$obj = New-Object -TypeName PSObject
#$obj | Add-Member -MemberType NoteProperty -name 'Domain' -value $domain
$obj | Add-Member -MemberType NoteProperty -name 'Name' -value $values[0]
$obj | Add-Member -MemberType NoteProperty -name 'IP Address' -value $values[1]
$obj | Add-Member -MemberType NoteProperty -name 'Alias' -value $values[2]
$obj | Add-Member -MemberType NoteProperty -name 'Timestamp' -value $timestamp
$objContainer += $obj
$values = #()
$named = 0
}
Write-Output $objContainer
$objContainer | Export-csv "dnslog_$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).csv" -NoTypeInformation
My question is, how to execute at once and looping the input from the text file after generate the dnslog(datetime).csv
e.g:
run the powershell script .\filename.ps1
input from serverlist1.txt
output dnslog(datetime).csv
input from serverlist2.txt
output dnslog(datetime).csv
input from serverlist3.txt
output dnslog(datetime).csv
input from serverlist4.txt
output dnslog(datetime).csv
input from serverlist5.txt
output dnslog(datetime).csv
Finish!
If i have more then 5 list of text file, it will continue to loop from the input file until completed.
Adding to Chris's answer I would also add a ReadCount flag to the Get-Content like so:
Get-Content -path "serverlist1.txt" -ReadCount 1 | % {
This will save having to read the entire file into memory.
You should consider running this a parallel batching job. Have you already tried doing so?
You can deal with the RAM busting problem by removing all those commits to memory (variable assignments and array rewriting with +=).
$timestamp = get-date
Get-Content -path "serverlist1.txt" | ForEach-Object {
$domain = $_
# You can clear this here.
$values = #()
$named = 0
# There are potentially better options than nslookup.
# Needs a bit of care to understand what's an alias here though.
# [System.Net.Dns]::GetHostEntry($domain)
# And if you don't like that, quite a few of us have written equivalent tools in PowerShell.
nslookup $domain 2>&1 | ForEach-Object {
if ($_ -match '^Name:\s*(.*)$') {
$values += $matches[1]
$named = 1;
} elseif (($_ -match '^.*?(\d*\.\d*\.\d*\.\d*)$') -and ($named -eq 1)) {
$values += $matches[1]
} elseif ($_ -match '^Aliases:\s*(.*)$') {
$values += $matches[1]
}
}
# Leave the output object in the output pipeline
# If you're running PowerShell 3 or better:
[PSCustomObject]#{
Domain = $domain
Name = $values[0]
'IP Address' = $values[1]
Alias = $values[2]
TimeStamp = $timestamp
}
# PowerShell 2 is less flexible. This or Select-Object.
#$obj = New-Object -TypeName PSObject
##$obj | Add-Member -MemberType NoteProperty -name 'Domain' -value $domain
#$obj | Add-Member -MemberType NoteProperty -name 'Name' -value $values[0]
#$obj | Add-Member -MemberType NoteProperty -name 'IP Address' -value $values[1]
#$obj | Add-Member -MemberType NoteProperty -name 'Alias' -value $values[2]
#$obj | Add-Member -MemberType NoteProperty -name 'Timestamp' -value $timestamp
# To leave this in the output pipeline, uncomment this
# $obj
# No version of PowerShell needs you to do this. It's a good way to ramp up memory usage
# for large data sets.
# $objContainer += $obj
} | Export-Csv "dnslog_$(Get-Date -Format 'MM-dd-yyyy_hh-mm-ss').csv" -NoTypeInformation

Powershell - loop with break/goto, recursive function?

Somewhat confused as what I need to use to acheive my goal. Here is what I need to do:
Recursively, get a list of files
Get the filename, lastwritedate, owner and append to an array
When the array reaches 500, output it to a csv filename.csv. Have a file for each 500 line output
Continue on, outputting files with details (500 lines) until end
I'm not conceptually grasping if I need a recursive function or can do some tricky while/do/for loop work.
Here is what I have so far:
$array = #()
$files = ls c:\users –recurse
$counter = 0
$filename = 0
foreach ($file in $files){
if($counter -ne 500){
write-host "Doing loop, counter is " $counter
$owner = Get-Acl $file.FullName
$data = new-object -type PSObject
$data | add-member -membertype noteproperty -name "File" -value $file.FullName
$data | add-member -membertype noteproperty -name "LastWriteTime" -value $file.LastWriteTime
$data | add-member -membertype noteproperty -name "Created" -value $file.CreationTime
$data | add-member -membertype noteproperty -name "Owner" -value $file.Owner
$array += $data
$counter ++
else($counter -eq 500){
$counter = 0
$array = #()
$array | export-csv "c:\temp\filelist\file-$filename.csv"
$filename ++
}
}
}
I've actually tried a bunch of different things, loop types etc. I just cant quiet figure out how to get the script to "goto start of for loop" as my understanding isn't there yet.
The reason I'm limiting to 500 lines is because this will run on a fileserver with millions of files, and a simple get-childitem wasn't quiet handling it gracefully.
I'm happy to not be given the direct answer, but given some hints as to what I need to do would be great (this isn't homework, I like learning powershell ha :))
I think I would nest a For loop within another For loop do to what you want. Something like:
For($x=0;$x -lt $Files.count;$x=$x+500){
$Array=#()
For($y=0;$y -lt 500; $y++){
$Array += $Files[($x+$y)] | Select FullName,LastWriteTime,CreationTime,#{l='Owner';e={($_|Get-ACL).Owner}}
}
$Array|Export-CSV C:\Path\To\Export-$x.csv -NoType
}

Powershell Export-Csv gives undesired result

Hello I hope you guys can help me with a problem that is bugging me for a couple of days now.
I cannot get the output right when I export the results of the script to csv file I get the following.
What I See
What I would like to see
function Get-ScheduledTask
{
[CmdletBinding()]
param(
[Parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[String[]]$ComputerName,
[Parameter(Mandatory=$false)]
[String[]]$RunAsUser,
[Parameter(Mandatory=$false)]
[String[]]$TaskName,
[parameter(Mandatory=$false)]
[alias("WS")]
[switch]$WithSpace
)
Begin
{
$Script:Tasks = #()
}
Process
{
$schtask = schtasks.exe /query /s $ComputerName /V /FO CSV | ConvertFrom-Csv
Write-Verbose "Getting scheduled Tasks from: $ComputerName"
if ($schtask)
{
foreach ($sch in $schtask)
{
if ($sch."Run As User" -match "$($RunAsUser)" -and $sch.TaskName -match "$($TaskName)")
{
Write-Verbose "$Computername ($sch.TaskName).replace('\','') $sch.'Run As User'"
$sch | Get-Member -MemberType Properties | ForEach -Begin {$hash=#{}} -Process {
If ($WithSpace)
{
($hash.($_.Name)) = $sch.($_.Name)
}
Else
{
($hash.($($_.Name).replace(" ",""))) = $sch.($_.Name)
}
} -End {
$script:Tasks += (New-Object -TypeName PSObject -Property $hash)
}
}
}
}
}
End
{
$Script:Tasks
}
}
$ComputerName = "SE94ABH02"
$ServiceAccounts = Get-Content "D:\Scripts\Test-Peter\Testing\ServiceAccounts.txt"
$obj = New-Object –TypeName PSObject
$obj | Add-Member -MemberType NoteProperty -Name ServerName -Value $ComputerName
$obj | Add-Member -MemberType NoteProperty -Name TaskName -Value ([string]::Join(",",(#())))
$obj | Add-Member -MemberType NoteProperty -Name ScheduledTaskState -Value ([string]::Join(",",(#())))
$obj | Add-Member -MemberType NoteProperty -Name LogonMode -Value ([string]::Join(",",(#())))
$obj | Add-Member -MemberType NoteProperty -Name Author -Value ([string]::Join(",",(#())))
$obj | Add-Member -MemberType NoteProperty -Name RunAsUser -Value ([string]::Join(",",(#())))
$obj | Add-Member -MemberType NoteProperty -Name ServiceName -Value ([string]::Join(",",(#())))
$obj | Add-Member -MemberType NoteProperty -Name StartName -Value ([string]::Join(",",(#())))
$SCHTSk = Get-ScheduledTask $ComputerName | Where-Object {$_.RunAsUser -like "NLKVKF94*"} | Select TaskName,ScheduledTaskState,LogonMode,Author,RunAsUser
If ($SCHTSK) {
$TEMP = #()
foreach ($TskItem in $SCHTSK) {
If ($TskItem -match "NLKVKF94") {
$TEMP += $TskItem
$info = #{
'TaskName'=$TEMP.TaskName;
'ScheduledTaskState'=$TEMP.ScheduledTaskState;
'LogonMode'=$TEMP.LogonMode;
'Author'=$TEMP.Author;
'RunAsUser'=$TEMP.RunAsUser
}
}
}
$tskobj = New-Object -TypeName PSObject -Property $info
$obj.TaskName += $tskobj.TaskName
$obj.ScheduledTaskState += $tskobj.ScheduledTaskState
$obj.LogonMode += $tskobj.LogonMode
$obj.Author += $tskobj.Author
$obj.RunAsUser += $tskobj.RunAsUser
}
$WmiObjectResultaat = Get-WmiObject -Class win32_service -computer $ComputerName | select-object __SERVER,Name,StartName
If ($WmiObjectResultaat) {
$TEMP = #()
foreach ($item in $WmiObjectResultaat) {
if ($ServiceAccounts -contains $Item.StartName) {
$TEMP += $Item
$info = #{
'Name'=$TEMP.Name;
'Startname'=$TEMP.Startname
}
}
}
$Srvobj = New-Object -TypeName PSObject -Property $info
$obj.ServiceName += $Srvobj.Name
$obj.StartName += $Srvobj.Startname
}
$obj | Export-Csv -Path "D:\Scripts\Test-Peter\Testing\lalala.csv" -NoTypeInformation
You've created a single object $obj to hold all the task details, instead of a collection of individual tasks:
$obj = New-Object –TypeName PSObject
$obj | Add-Member -MemberType NoteProperty -Name ServerName -Value $ComputerName
$obj | Add-Member -MemberType NoteProperty -Name TaskName -Value ([string]::Join(",",(#())))
# ... and so on
Again, instead of adding individual tasks to an array/collection, you add the value of each property to the same property of $obj:
$tskobj = New-Object -TypeName PSObject -Property $info
$obj.TaskName += $tskobj.TaskName
$obj.ScheduledTaskState += $tskobj.ScheduledTaskState
$obj.LogonMode += $tskobj.LogonMode
$obj.Author += $tskobj.Author
$obj.RunAsUser += $tskobj.RunAsUser
All you need to do is change $obj to an empty collection and assign the tasks to it instead:
$obj = #()
foreach($Task in (Get-ScheduledTask)){
$TaskProperties = #{
'TaskName'=$Task.TaskName;
'ScheduledTaskState'=$Task.ScheduledTaskState;
'LogonMode'=$Task.LogonMode;
'Author'=$Task.Author;
'RunAsUser'=$Task.RunAsUser
}
$obj += New-Object psobject -Property $TaskProperties
}
$obj | Export-Csv "output.csv"