Powershell script output to Discord - powershell

removing source for Proprietary reasons
ERROR
Out-String : A positional parameter cannot be found that accepts argument 'System.Object[]'.
At line:176 char:28
content = $summaries | Out-String `

Note: This isn't a complete answer, but it addresses your immediate problem - broken [pscustomobject] literal syntax - and provides some background information.
Format-Table, as all Format-* cmdlets, is only intended to produce output for display as opposed to programmatic processing.
To produce data (objects), use the Select-Object, which uses the same syntax with respect to the -Property parameter as Format-Table, notably including the calculated properties your Format-Table call uses.
Select-Object outputs [pscustomobject] instances that each have the specified properties.
Thus, you may be looking for this:
# Note The ` at the end of the line continues the Select-Object call
# on the next line, and the following lines are an array of
# property names / calculated properties that are passed
# to the positionally implied -Property parameter
$payload = $summaries | Select-Object `
start,
end,
#{ Label = 'issued_amount'; Expression = { '{0:C0}' -f $_.issued_amount }; Align = 'right' },
#{ Label = 'maturing_amount'; Expression = { '{0:C0}' -f $_.maturing_amount }; Align = 'right' },
#{ Label = 'inflation_compensation'; Expression = { '{0:C0}' -f $_.inflation_compensation }; Align = 'right' },
#{ Label = 'secondary_purchased'; Expression = { '{0:C0}' -f $_.secondary_purchased_amount }; Align = 'right' },
#{ Label = 'secondary_sold'; Expression = { '{0:C0}' -f $_.secondary_sold_amount }; Align = 'right' },
#{ Label = 'change'; Expression = { '{0:$#,##0}' -f $_.change }; Align = 'right' }
$payload is now an array of [pscustomobject] instances with the specified properties.
However, it looks like you cannot pass such an array directly to Send-DiscordMessage, except perhaps via the -Text parameter, which requires you to create a formatted string representation via Out-String:
Send-DiscordMessage -Uri $hookUrl -Text ($payload | Out-String)

Related

Set a PSObject path using an array for the "dot" variable names

I have a PSObject that I have filled with a json structure. I need to be able to set the value of one of the entries in the tree using an array that has the names nodes of the json path. Here is an example that gets close, but does not ultimately work (but helps explain what I am looking for):
$json = #"
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
"#
$settings = $json | ConvertFrom-Json
[System.Collections.ArrayList] $jsonPath = New-Object -TypeName "System.Collections.ArrayList"
$jsonPath.Add("Logging") | Out-Null
$jsonPath.Add("LogLevel") | Out-Null
$jsonPath.Add("Microsoft") | Out-Null
Write-Output "Old Value was $($settings.$($jsonPath[0]).$($jsonPath[1]).$($jsonPath[2]))"
# I need a way to set this value when there could be an unknown number of elements in the array.
$settings.$($jsonPath[0]).$($jsonPath[1]).$($jsonPath[2]) = "Debug"
Write-Output "New Value is $($settings.$($jsonPath[0]).$($jsonPath[1]).$($jsonPath[2]))"
This works if I know that the $jsonPath array will have 3 elements. But it could have many more or less.
I thought to iterate the array like this:
$result = $settings
foreach ($pathItem in $jsonPath)
{
$result = $result.$pathItem
}
$result = "Debug"
But this just sets the string value of $result. Not the value in $settings.
I feel like I need a way to get a reference of the $setting.$pathItem value (rather than the actual value), so that I can make sure I set that value on the $settings variable.
How can I update $settings using the values in the array as the dot de-referencers?
Assuming you fully control or implicitly trust the content of array (list) $jsonPath, Invoke-Expression - which is generally to be avoided - offers a simple solution:
$jsonPath = 'Logging', 'LogLevel', 'Microsoft'
Invoke-Expression "`$settings.$($jsonPath -join '.') = 'Debug'"
Note: If there's a chance $jsonPath contains nonstandard property names (e.g. with spaces), use the following instead:
Invoke-Expression "`$settings.$($jsonPath.ForEach({ '{' + $_ + '}' }) -join '.') = 'Debug'"
Iterating through the path array is a sound option in my opinion, you only need to change your logic a bit in order to update the property:
$jsonPath = 'Logging\LogLevel\Microsoft'.Split('\')
$settings = $json | ConvertFrom-Json
$ref = $settings
foreach($token in $jsonPath) {
# if this token is not the last in the array
if($token -ne $jsonPath[-1]) {
# we can safely get its value
$ref = $ref.$token
continue
}
# else, this is the last token, we need to update the property
$ref.$token = 'newValue'
}
$settings.Logging.LogLevel.Microsoft # has newValue

How to Pass User Input to a JSON File using powershell

I am creating a PowerShell script using Convert To-JSON cmd and i achieved that using below
$body = #{
devices = #(
#{
credentials = #(
#{
label = 'username'
value = 'myname'
sensitive = 'false'
},
#{
label = 'Password'
value = 'Password#!'
sensitive = 'true'
}
)
services = #(
#{
name = 'RDP'
url = "https://$inputIpAddress/?pauth=[proxy_token]&data=[connection:$inputUsername]"
instructor = 'false'
},
#{
name = 'HTTPS'
url = "https://$inputIpAddress/?pauth=[proxy_token]&data=[connection:$inputUsername]"
instructor = 'false'
},
#{
name = 'SSH'
url = "https://$inputIpAddress/?pauth=[proxy_token]&data=[connection:$inputUsername]"
instructor = 'false'
}
connections = #(
#{
id = 'myname-rdp'
protocol = 'rdp'
hostname = "192.168.1.6"
port ='3389'
}
)
Parameters = #(
#{
name = 'username'
value = 'myname'
},
#{
name = 'password'
value = 'Password#!'
}
)
}
)
}
i am converting the above powershell to JSON File($body | ConvertTo-Json -Depth 4) and i would like to replace pass the arguments for the Username and IP Address and store it with the username.json every time while converting to JSON.
i have tried the Read-host to get the input from the user but i am facing difficulty to pass that input before printing the output.
For the IP address, you can set the new value by defining the parameter using standard dot-notation.
In the case of username, because Parameters is an array with duplicate object names, doing $body.devices.Parameters.name would return both the username and password objects, so you can filter the array to only select the object where the name is username
$inputUsername = Read-Host "Input Username"
$inputIpAddress = Read-Host "Input IP address"
( $body.devices.Parameters | Where-Object { $_.name -eq 'username' } ).value = $inputUsername
$body.devices.connections.hostname = $inputIpAddress
$body | ConvertTo-Json -Depth 4
As an interpreted language, PowerShell allows you to construct literals based on values that are determined at runtime.
Therefore, simply place your hashtable literal (#{ ... }) after your Read-Host calls, and reference the variables in which you store the Read-Host responses in that literal.
A simplified example:
# Simulate two Read-Host calls (hard-code sample values).
$foo = 42
$bar = 'hi'
# Now define the hashtable literal and reference the variables assigned to above.
# Note: An [ordered] hashtable is used in order to retain the entries
# in definition order.
[ordered] #{
devices = #{
foo = $foo
bar = $bar
}
}
Piping the above to ConvertTo-Json -Depth 4 yields:
{
"devices": {
"foo": 42,
"bar": "hi"
}
}
Note how the values of $foo and $bar were incorporated. You could even use the same technique in a loop, provided the hashtable literal is also in the loop body (after setting the variables it references).

Powershell Most efficient way to combine customobject

I'm parsing a webpage, but having difficulty combining into one variable.
I'm looking for the most efficient way to do so as well.
This is code I have so far. Any help is appreciated.
$WebResponse = Invoke-WebRequest -Uri "https://finviz.com/news.ashx"
$title = ($WebResponse.AllElements | Where {$_.class -match 'nn-tab-link'}).innertext
$time = ($WebResponse.AllElements | Where {$_.class -match 'nn-date'}).innertext
$link = ($WebResponse.AllElements | Where {$_.class -match 'nn-tab-link'}).href
$r = {
[PSCustomObject]#{
{Time(E/T)} = $time
Headline = $title
Link = $link
}
}
$R
Thanks
Use an index-based loop (which assumes that all three arrays have corresponding elements and the same element count):
$objects = foreach ($i in 0..($title.Count-1)) {
[pscustomobject] #{
'Time(E/T)' = $time[$i]
Headline = $title[$i]
Link = $link[$i]
}
}
Note how property name Time(E/T) is enclosed in '...' - a verbatim string - rather than in {...} - a script block; the latter only works accidentally, because script blocks stringify[1] to their verbatim content (without the { and }).
[1] When using the [pscustomobject] #{ ... } syntactic sugar, the keys of the hashtable (#{ ... }) are implicitly stringified, given that objects' property names are invariably strings.

PowerShell, How to provide a pipe variable?

This is a high level question as the details might not be precise, as I'm not in my office but home.
I have a function that accept variables through pipe:
get-csv | myfunc
The pipe source is the fields from a .csv file.
How to define a variables and pipe into myfunc()? Would HashTable be good?
$my_pipe_variables = #{ Color = ‘Red’; Doors = 4; Convertible = $false}
$my_pipe_variables | myfunc
would that be the correct syntax?
Update:
I finally get around to try it but it is not working for me, as my myfunc accesses pipe variables directly via $_. Here is the demo:
function showThem { echo Color: $_.Color }
> [pscustomobject]#{ Color = ‘Red’; Doors = 4; Convertible = $false} | showThem
Color:
How can I make it works for myfunc, which accesses pipe variables directly via $_?
Import-Csv (not Get-Csv), for reading CSV data from a file, and ConvertFrom-Csv, for reading CSV data from a string, output a collection of custom objects (type [pscustomobject]) whose properties reflect the CSV data's columns.
To construct such custom objects on demand in order to simulate Import-Csv / ConvertFrom-Csv input, use the [pscustomobject] #{ <propertyName>=<value>; ... } syntax (PSv3+).
E.g., to simulate 2 rows of CSV data with columns Color, Doors,
and Convertible:
[pscustomobject] #{ Color = 'Red'; Doors = 4; Convertible = $false },
[pscustomobject] #{ Color = 'Blue'; Doors = 5; Convertible = $false } |
...
Separately, in order to make a function process input from the pipeline object by object via automatic variable $_, it must have a process { ...} block - see help topic about_Functions.
# Define the function body with a process { ... } block, which
# PowerShell automatically calls for each input object from the pipeline,
# reflected in automatic variable $_
function showThem { process { "Color: " + $_.Color } }
[pscustomobject] #{ Color = 'Red'; Doors = 4; Convertible = $false },
[pscustomobject] #{ Color = 'Blue'; Doors = 5; Convertible = $false } |
showThem
Note: In PowerShell, echo is an alias of Write-Output, whose explicit use is rarely needed; instead, the function relies on PowerShell's implicit output: the result of the string concatenation (+) implicitly becomes the function's output.
The above yields:
Color: Red
Color: Blue

How to colorise PowerShell output of Format-Table

I try to colorise the column RAM in red if the value is greater than 100 MB:
Get-Process | Format-Table #{ Label = "PID"; Expression={$_.Id}},
#{ Label = "Name"; Expression={$_.Name}},
#{ Label = "RAM (MB)"; Expression={[System.Math]::Round($_.WS/1MB, 1)}},
#{ Label = "Responding"; Expression={$_.Responding}}
I try with Write-Host -nonewline, but the result is wrong.
Get-Process | Format-Table #{ Label = "PID"; Expression={$_.Id}},
#{ Label = "Name"; Expression={$_.Name}},
#{ Label = "RAM (MB)"; Expression={write-host -NoNewline $([System.Math]::Round($_.WS/1MB, 1)) -ForegroundColor red}},
#{ Label = "Responding"; Expression={ write-host -NoNewline $_.Responding -fore red}}
Starting with PowerShell 5.1 or later you can use VT escape sequences to add colors to a single column, but only if your console supports VT escape sequences (e.g. Windows 10 Fall Creators Update, Linux or Mac, but not Windows 8 w/o a console emulator like ConEmu).
Here is an example that has the formatting specified in an expression, though the same could be used in a ps1xml file:
dir -Exclude *.xml $pshome | Format-Table Mode,#{
Label = "Name"
Expression =
{
switch ($_.Extension)
{
'.exe' { $color = "93"; break }
'.ps1xml' { $color = '32'; break }
'.dll' { $color = "35"; break }
default { $color = "0" }
}
$e = [char]27
"$e[${color}m$($_.Name)${e}[0m"
}
},Length
And the resulting output, note that the column width looks good, there are no extra spaces from the escape sequence characters.
The accepted answer is incorrect, it is possible to colorize columns. The solution to getting conditional column colors is to use Write-PSObject.
Here are some wonderful examples with documented code and explanations.
From the above resource:
Write-PSObject $servers -MatchMethod Exact -Column "Manufacture" -Value "HP" -ValueForeColor Yellow -ValueBackColor Red -RowForeColor White -RowBackColor Blue;
I found this via a GitHub issue to add color formatting to Format-Table, which seems to be a feature PowerShell devs would like to add at some point.
You could colorize the row making use of a regular expression...
filter colorize-row{
Get-Process | Select-Object Id, Name, WS, Responding | foreach {
# Print 'red' row if WS greater than 100 MB
if([System.Math]::Round($_.WS/1MB,1) -match "^([0-9]|[0-9][0-9]|[1-9][0-9]?$|^100$)$"){
[console]::ForegroundColor="white"; $_;
} else {
[console]::ForegroundColor="red"; $_;
}
}
}
colorize-row
Output:
The quick answer is that you can't. It's possible to use Write-Host with colors, but there's no "output" to send to format-table.
The "output" from Write-Host is a side-effect that sends data directly to the console rather than returning it to the caller like a standard function.
In conjunction with the comment by #David Martin, here's a link with an interesting pattern-matching format-color function.
you can install module PsWrite
Write-color
he also has other smart funtions like
Write-logstep
Write-ProgressbarreColor
Yes, you can use ANSI Escape colors and have the colors conditional:
Get-Process | Format-Table #{ Label = "PID"; Expression={$_.Id}},
#{ Label = "Name"; Expression={$_.Name}},
#{ Label = "RAM (MB)"; Expression={if($_.WS/1MB -gt 100){"$([char]27)[0;31m$([System.Math]::Round($_.WS/1MB, 1))$([char]27)[0m"}else{"$([char]27)[0;32m$([System.Math]::Round($_.WS/1MB, 1))$([char]27)[0m"}}},
#{ Label = "Responding"; Expression={if($_.Responding -eq $true){"$([char]27)[0;32m$($_.Responding)$([char]27)[0m"}else{"$([char]27)[0;31m$($_.Responding)$([char]27)[0m"}}}