Powershell: Extract part of values and output by new format - powershell

I'm currently studying Powershell and working on a script that grabs Display configuration from windows system. I get 2 question:
Question 1. The script is:
"Display Config" >> system1.txt
"----------------------------------------------------------------------------
---------------------------------" >> system1.txt
add-type -assemblyName system.windows.forms
[system.windows.forms.screen]::AllScreens | Format-List Bounds | out-file -append system1.txt
The output is the result of resolution of 2 monitors, just like that:
Bounds : {X=0,Y=0,Width=1920,Height=1080}
Bounds : {X=1920,Y=0,Width=2560,Height=1440}
But I just want to extract values of 'Width' and 'Height', and the make output show like:
Width:1920
Height:1080
Width:2560
Height:1440
Question2: For this script:
Get-WmiObject WmiMonitorconnectionparams -Namespace root\wmi | format-List
VideoOutputTechnology | out-file -append system1.txt
I get results:
VideoOutputTechnology : 10
VideoOutputTechnology : 4
But the value 4 and 10 need to be decoded, ie '10 = Displayport External' according to an url: https://technet.microsoft.com/en-us/ff546605(v=vs.89)
How could I decode the value according to the URL and make the result only show like 'Displayport External' in the output txt?
I would greatly appreciate your reply.

Question 1:
LotPings has effectively provided a solution in a comment (PSv3+):
[system.windows.forms.screen]::AllScreens.Bounds |
Format-List Width, Height >> system1.txt
.Bounds is directly applied to the array returned by ::AllScreens, in which case an array of the array's elements' respective .Bounds property values is conveniently returned, which is a PSv3+ feature called member-access enumeration.
Format-List Width, Height then extracts the .Width and .Height property values from the resulting [System.Drawing.Rectangle] instances and displays them as a list.
Note: The purpose of all Format-* cmdlets is to create output for display only, i.e., to produce output that is friendly to the human observer, but ill-suited for further programmatic processing.
Since you're using Out-File -Append without additional options, >> is a convenient shortcut. (You'll get UTF16-LE-encoded ("Unicode") files.)
Question 2:
PowerShell has great built-in support for .NET enumerations ([enum]-derived types), but what WMI reports in your case are simple integers [System.UInt32], so you have to perform your own mapping.
In PSv5+ you can define your own [enum] types, however, in which case a simple cast to that type can help you:
enum VideoOutputTechnology {
D3DKMDT_VOT_UNINITIALIZED = -2
D3DKMDT_VOT_OTHER = -1
D3DKMDT_VOT_HD15 = 0
# ...
D3DKMDT_VOT_DVI = 4
D3DKMDT_VOT_DISPLAYPORT_EXTERNAL = 10
# ...
}
In PSv4- you can use `Add-Type -TypeDefinition` to define the enum via a string containing a _C#_ enum definition.
Note:
I've retained the original symbolic constant names from https://technet.microsoft.com/en-us/ff546605(v=vs.89), but you're free to rename to something friendlier; e.g., D3DKMDT_VOT_DISPLAYPORT_EXTERNAL -> Displayport_External - but note that embedded spaces and special characters are not allowed. If that isn't friendly enough, consider Theo's helpful solution.
You're creating a static copy of the symbolic constants, so the two sets could get out of sync, though new constants are probably added only rarely.
(I'm not aware of any preexisting .NET enum type that defines these constants and they seem to be defined in a *.h file that you cannot assume to be present on every machine; you could web-scrape the URL, but that is brittle.)
You can then apply the cast in the context of a calculated property in order to translate the raw integer into its symbolic name:
Get-WmiObject WmiMonitorconnectionparams -Namespace root\wmi |
Format-List #{
n='VideoOutputTechnology'
e={ [VideoOutputTechnology] $_.VideoOutputTechnology }
} >> system1.txt
This should yield:
VideoOutputTechnology : D3DKMDT_VOT_DISPLAYPORT_EXTERNAL
VideoOutputTechnology : D3DKMDT_VOT_DVI

As for the VideoOutputTechnology values. Yes, they are values from the D3DKMDT_VIDEO_OUTPUT_TECHNOLOGY enumeration, but i believe you want to return a more descriptive string. In that case you could use a small function to translate the value to string:
function Resolve-VideoOutputTechnology {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true, Mandatory = $true, Position = 0)]
[int64]$VideoOutputTechnology
)
switch ($VideoOutputTechnology) {
-2 { return 'Uninitialized connector' } # D3DKMDT_VOT_UNINITIALIZED
-1 { return 'Unknown connector' } # D3DKMDT_VOT_OTHER
0 { return 'VGA connector' } # D3DKMDT_VOT_HD15
1 { return 'S-video connector' } # D3DKMDT_VOT_SVIDEO
2 { return 'Composite video connector' } # D3DKMDT_VOT_COMPOSITE_VIDEO
3 { return 'Component video connector' } # D3DKMDT_VOT_COMPONENT_VIDEO
4 { return 'Digital Video Interface (DVI) connector' } # D3DKMDT_VOT_DVI
5 { return 'High-Definition Multimedia Interface (HDMI) connector' } # D3DKMDT_VOT_HDMI
6 { return 'Low Voltage Differential Swing (LVDS) or Mobile Industry Processor Interface (MIPI) Digital Serial Interface (DSI) connector' } # D3DKMDT_VOT_LVDS
8 { return 'D-Jpn connector' } # D3DKMDT_VOT_D_JPN
9 { return 'SDI connector' } # D3DKMDT_VOT_SDI
10 { return 'External display port' } # D3DKMDT_VOT_DISPLAYPORT_EXTERNAL
11 { return 'Embedded display port' } # D3DKMDT_VOT_DISPLAYPORT_EMBEDDED
12 { return 'External Unified Display Interface (UDI)' } # D3DKMDT_VOT_UDI_EXTERNAL
13 { return 'Embedded Unified Display Interface (UDI)' } # D3DKMDT_VOT_UDI_EMBEDDED
14 { return 'Dongle cable that supports SDTV connector' } # D3DKMDT_VOT_SDTVDONGLE
15 { return 'Miracast connected session' } # D3DKMDT_VOT_MIRACAST
0x80000000 { return 'Internally display device (the internal connection in a laptop computer)' } # D3DKMDT_VOT_INTERNAL
default { return 'Unknown connector' }
}
}

Related

Unable to Return [BindingList[RandomType]] from ScriptProperty

I have a complex class that dynamically adds members to itself based on a file loaded via Import-Clixml.
Boiling the class down to the problematic part leaves us with this (Take note of the commented line used to prove success up to that point):
class TestClass {
[hashtable]$_data = #{}
[void]DefineData([object]$data) {
$this._data['Data'] = $data
$this | Add-Member -MemberType ScriptProperty -Name 'ScriptProperty' -Value {
#$this._data['Data'].GetType() | Out-Host
return $this._data['Data']
}
}
}
In the following code, there are 4 statements for assigning a value to $OriginalValue. Leave 3 of these statements commented and uncomment the one you want to try. When executed, the code should result in $ReturnValue containing the same value as $OriginalValue, but in the case of assigning $OriginalValue an instance of [BindingList[RandomType]], $ReturnValue is $null.
$ClassVar = [TestClass]::new()
$OriginalValue = [System.ComponentModel.BindingList[string]]::new()
#$OriginalValue = #{}
#$OriginalValue = [PSCustomObject]#{ Name = 'Value' }
#$OriginalValue = "Test String"
$OriginalValue.GetType()
$ClassVar.DefineData($OriginalValue)
$ReturnValue = $ClassVar.ScriptProperty
$ReturnValue.GetType()
Yes, I can hack my way around the problem by storing instances of [BindingList[RandomType]] in a [hashtable], but could someone explain what is going on, or even better yet, how to fix the code for all data types?
As explained in comments, the problem is not the BindingList but the output from your Script Block being enumerated. Since your BindingList has no elements when you call .DefineData($OriginalValue) then enumerating a list with no elements via .ScriptProperty results in null value:
(& { [System.ComponentModel.BindingList[string]]::new() }).GetType()
# Errors with:
# InvalidOperation: You cannot call a method on a null-valued expression.
A simple workaround is to wrap the output in a single element array before outputting, for this you can use the comma operator ,.
(& { , [System.ComponentModel.BindingList[string]]::new() }).GetType()
# Output type is preserved and the wrapping array is lost due to enumeration
So, your class method could look as follows considering the hashtable property is not needed:
class TestClass {
[void] DefineData([object] $data) {
$this.PSObject.Properties.Add(
[psscriptproperty]::new(
'ScriptProperty',
{ , $data }.GetNewClosure()
)
)
}
}

Convert Single Line to Multiple Lines

I am new to this Powershell.
I am trying to learn how to modified output.
When I run "Write-output $result | format-list" I have the following output
userDetails : #{id=AA:BB:CC:DD:11:22; connectionStatus=CONNECTED; hostType=WIRELESS;
authType=WPA2/WPA3+802.1x/FT-802.1x}
connectedDevice : {#{deviceDetails=}}
How do I rewrite this output to below using powershell 7.2 ? I would like to have
userDetails :
connectionStatus= CONNECTED
hostType = WIRELESS
authType = WPA2/WPA3+802.1x/FT-802.1x
connectedDevice :
Thank you for your help.
Note: I'm assuming that you're looking for a friendlier display representation of your data. For programmatic processing, Format-* cmdlets should be avoided, for the reasons explained in this answer.
What you're looking for is for Format-List to work recursively, i.e. to not only list the individual properties and their values for each input object itself, but also for nested objects contained in property values.
Format-List does not support this:
Nested objects are represented by their single-line .ToString() representations.
If they're part of a collection (enumerable), the individual elements' representations are joined with , on a single line, and are enclosed in {...}(!) as a whole. How many elements are shown at most is controlled by the $FormatEnumerationLimit preference variable, which defaults to 4.
However, you can approximate recursive listing behavior with Format-Custom; using a simplified example:
# Nested sample object to format.
[pscustomobject]#{
userDetails = [pscustomobject] #{
id = 'AA:BB:CC:DD:11:22'
connectionStatus= 'CONNECTED'
hostType = 'WIRELESS'
authType = 'WPA2/WPA3+802.1x/FT-802.1x'
}
connectedDevice = '...'
} |
Format-Custom -Depth 1 # use higher -Depth levels for multi-level expansion
Output:
class PSCustomObject
{
userDetails =
[
class PSCustomObject
{
id = AA:BB:CC:DD:11:22
connectionStatus = CONNECTED
hostType = WIRELESS
authType = WPA2/WPA3+802.1x/FT-802.1x
}
]
connectedDevice = ...
}
Note:
Caveat: If a custom view happens to be defined for a given input object's type via associated formatting data, it is that custom view that Format-Custom will invoke, not the structural representation shown above; however, this is rare ([datetime] is a rare example).
Apart from the output showing the structure recursively, the format differs from that of Format-List as follows:
Complex objects are enclosed in class <typeName> { ... }
Elements of collections (enumerables) each render on their own (group of) line(s), enclosed in [ ... ] overall. However, as with Format-List, the number of elements that are shown at most is limited by $FormatEnumerationLimit.
To prevent excessively nested output, Format-Custom stops recursing at a depth of 5 by default; you can control the recursion depth via the -Depth parameter, 1 meaning that only objects in immediate child properties are expanded.
When the recursion depth limit is reached, non-collection objects are represented by their .ToString() representations, as with Format-List.
Here is some code that produces output close to your desired output:
# Create sample data
$result = [pscustomobject] #{
userDetails = [pscustomobject]#{ id="AA:BB:CC:DD:11:22"; connectionStatus="CONNECTED"; hostType="WIRELESS"; authType="WPA2/WPA3+802.1x/FT-802.1x"}
connectedDevice = [pscustomobject]#{ deviceDetails=$null }
}
# Produce output
"userDetails :"
($result.userDetails |
Format-List -Property connectionStatus, hostType, authType |
Out-String).Trim() -replace '(?m)(?<=^[^:]+):', '='
"`nconnectedDevice :"
# TODO: add similar code as for .userDetails
Output:
userDetails :
connectionStatus = CONNECTED
hostType = WIRELESS
authType = WPA2/WPA3+802.1x/FT-802.1x
connectedDevice :
Using member access .userDetails to select a child object (similar to Select-Object -ExpandProperty userDetails).
Using Format-List -Property to output a list of the given properties
Using Out-String to create a string from the formatting data that is produced by Format-List. This string looks exactly like the output you normally see on the console.
Use String method .Trim() to remove whitespace (in this case newlines) from the beginning and end.
Use the -replace operator to replace the first : of each line by =. See this regex101 demo for more information.

How to use a powershell function to return the expected value?

As we know, PowerShell has wacky return semantics.
Function return value in PowerShell shows there are two main ideas to wrap my head around:
All output is captured, and returned
The return keyword just indicates a logical exit point
Even things like reserving variables in outer scopes cause output, like [boolean]$isEnabled. Another good one is $someCollection.Add("toto") which spits the new collection count. Even Append() function causes output.
For example :
Function MyFunc {
$res1 = new-object System.Text.StringBuilder
$res1.Append("titi");
$res2 = "toto"
return $res2
}
$s = MyFunc
Write-Host $s
The output is : titi toto.
The expected output should be toto.
How to use a powershell function to return the expected value? (at least when viewed from a more traditional programming perspective)
Change
$res1.Append("titi");
to
$res1.Append("titi") | Out-Null
because the function returns every output which otherwise would be visible in the console.
if by using 'toto' you are trying to understand if your function succeeded, you could do
Function MyFunc {
$res1 = new-object System.Text.StringBuilder
$res1.Append("titi") | Out-Null
return $?
}
"$?" returns a boolean if the previous command succeeded (true) or failed (false). so externally it would look like
$s = MyFunc
if ($s) {
Write-Host "successful" -Foregroundcolor Green
}
else {
Write-Error "unsuccessful"
}
When PowerShell was being developed, the team wanted to make it simple to use. But, it was confusing to people who know return from other languages. The implementation in classes is an attempt to rectify that mistake.
The return keyword works very differently in methods in PowerShell classes. It works like the return statements in other languages.
In a class method, the return keyword:
Exits the current scope.
Returns the associated object (return ).
Returns only the associated object.
The object that Return returns must match the return type of the method.
It is consistent with the return keyword and analogous keywords in other languages.
class ClassMyFunc
{
[string] MyFunc
{
$res1 = new-object System.Text.StringBuilder
$res1.Append("titi")
$res2 = "toto"
return $res2
}
}
$cmf = New-Object -TypeName ClassMyFunc
$cmf.MyFunc()
The output is : toto, as expected.
Using classes solved my problem, without having to search all functions returning a value in the console and piping it to Out-Null (as suggested by #TobyU).

Writing a hashtable value to an attribute

Powershell newbie here, my first script.
I have user objects with an AD custom attribute named tvCode with a values of 123456 or 6787682 or 983736 etc.
I would like to script something that will get the tvCode value from the user object
When:
123456 = Sony
6787682 = Samsung
9837343 = LG
Write the value of "Sony" or "Samsung" or "LG" to the "City" attribute of the user object.
Looks like i may need to use a hashtable.
If possible do this for a specific OU
hope this makes sense
thanks
function Convert-TVCode {
Param
(
[parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]
[String[]]
$Code
)
Process {
foreach ($C in $Code) {
switch ($C) {
"123456" {$brand = "Sony"}
"6787682" {$brand = "Samsung"}
"9837343" {$brand = "LG"}
default {
$brand = $null
Write-Warning "$C not included in switch statement. Returning"
return
}
}
if ($brand) {
Write-Verbose "Code '$C' matched to Brand '$brand' -- searching for users to update"
Get-ADUser -Filter "tvCode -eq '$C'" | Set-ADUser -Replace #{tvCode=$brand}
}
}
}
}
This function will allow you to update any users that have their tvCode attribute set as one of the target numerical values. You can have it hit multiple codes at once as well.
Examples:
Convert-TVCode -Code 123456
Convert-TVCode -Code 123456,6787682
Convert-TVCode -Code 123456,6787682,9837343 -Verbose
Update the switch statement in the function to customize it to your actual values and let me know if you have any questions!

Parse powershell script parameters

Is there an easy way to parse the params from a powershell script file
param(
[string]$name,
[string]$template
)
I have started reading the file and wondered if there is a better way, maybe by a help/man command?
class PowerShellParameter {
public string Name;
public string Type;
public string Default;
}
string[] lines = File.ReadAllLines(path);
bool inparamblock = false;
for (int i = 0; i < lines.Length; i++) {
if (lines[i].Contains("param")) {
inparamblock = true;
} else if (inparamblock) {
new PowerShellParameter(...)
if (lines[i].Contains(")")) {
break;
}
}
}
There are at least two possibilies. First one (imho better): use Get-Command:
# my test file
#'
param(
$p1,
$p2
)
write-host $p1 $p2
'# | Set-content -path $env:temp\sotest.ps1
(Get-Command $env:temp\sotest.ps1).parameters.keys
For all members look at
Get-Command $env:temp\sotest.ps1 | gm
#or
Get-Command $env:temp\sotest.ps1 | fl *
The other (harder way) is to use regular expression
[regex]::Matches((Get-Help $env:temp\sotest.ps1), '(?<=\[\[-)[\w]+') | select -exp Value
I like the solution with Get-Command proposed by #stej. Unfortunately it does not work if script parameters have explicit types specified and an assembly of such a type is not yet loaded into the session. That is why I still use this script: Get names of script parameters
I'm not really sure what you're after, is it documenting your scripts? In that case have a look at Get-Help about_Comment_Based_Help. It will tell you how to do that, and after that you can use Get-Help on your script/module.
If you're after more strict parameter handling, take a look at about_functions_advanced_parameters and about_functions_cmdletbindings on how to better structure parameters. For example,
[Parameter(Position=0,Mandatory=$true,HelpMessage='Enter
architecture("OSX","WinXP","Win7","Linux")')]
[ValidateSet("OSX","WinXP","Win7","Linux")]
[string]$architecture
will make that parameter mandatory, read from position 0 of the command, allow only a value from the given set, and give a brief help message when asking for input if that parameter was not given.