Sort by ascending number order - powershell

$var_1text = $var_2text = $var_17text = $null
Get-Variable -Name var_*
I get the following output : 1-17-2
var_1text
var_17text
var_2text
But I want the following output : 1-2-17
var_1text
var_2text
var_17text

Use the Sort-Object cmdlet:
Get-Variable -Name var_* |Sort-Object { ($_.Name -replace "[^\d]","") -as [int] }

Along the same lines
Get-Variable -Name var_* | Select-Object *,#{L="NameIndex";E={[void]($_.Name -match '\d+');[int]$Matches[0]}} | Sort-Object NameIndex
You can create a calculated property that only contains the number portion. Cast it to [int] and sort on that property. This would be especially useful if you need to refer to this more than once in the code as supposed to recalling the regex.

Related

powershell winform searchbox shows results incorrect [duplicate]

I'm using Powershell to set up IIS bindings on a web server, and having a problem with the following code:
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if ($serverIps.length -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
$primaryIp = $serverIps[0]
$secondaryIp = $serverIps[1]
If there's 2+ IPs on the server, fine - Powershell returns an array, and I can query the array length and extract the first and second addresses just fine.
Problem is - if there's only one IP, Powershell doesn't return a one-element array, it returns the IP address (as a string, like "192.168.0.100") - the string has a .length property, it's greater than 1, so the test passes, and I end up with the first two characters in the string, instead of the first two IP addresses in the collection.
How can I either force Powershell to return a one-element collection, or alternatively determine whether the returned "thing" is an object rather than a collection?
Define the variable as an array in one of two ways...
Wrap your piped commands in parentheses with an # at the beginning:
$serverIps = #(gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort)
Specify the data type of the variable as an array:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
Or, check the data type of the variable...
IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
Force the result to an Array so you could have a Count property. Single objects (scalar) do not have a Count property. Strings have a length property so you might get false results, use the Count property:
if (#($serverIps).Count -le 1)...
By the way, instead of using a wildcard that can also match strings, use the -as operator:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
You can either add a comma(,) before return list like return ,$list or cast it [Array] or [YourType[]] at where you tend to use the list.
If you declare the variable as an array ahead of time, you can add elements to it - even if it is just one...
This should work...
$serverIps = #()
gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort | ForEach-Object{$serverIps += $_}
You can use Measure-Object to get the actual object count, without resorting to an object's Count property.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if (($serverIps | Measure).Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
Return as a referenced object, so it never converted while passing.
return #{ Value = #("single data") }
I had this problem passing an array to an Azure deployment template. If there was one object, PowerShell "converted" it to a string. In the example below, $a is returned from a function that gets VM objected according to the value of a tag. I pass the $a to the New-AzureRmResourceGroupDeployment cmdlet by wrapping it in #(). Like so:
$TemplateParameterObject=#{
VMObject=#($a)
}
New-AzureRmResourceGroupDeployment -ResourceGroupName $RG -Name "TestVmByRole" -Mode Incremental -DeploymentDebugLogLevel All -TemplateFile $templatePath -TemplateParameterObject $TemplateParameterObject -verbose
VMObject is one of the template's parameters.
Might not be the most technical / robust way to do it, but it's enough for Azure.
Update
Well the above did work. I've tried all the above and some, but the only way I have managed to pass $vmObject as an array, compatible with the deployment template, with one element is as follows (I expect MS have been playing again (this was a report and fixed bug in 2015)):
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
foreach($vmObject in $vmObjects)
{
#$vmTemplateObject = $vmObject
$asJson = (ConvertTo-Json -InputObject $vmObject -Depth 10 -Verbose) #-replace '\s',''
$DeserializedJson = (New-Object -TypeName System.Web.Script.Serialization.JavaScriptSerializer -Property #{MaxJsonLength=67108864}).DeserializeObject($asJson)
}
$vmObjects is the output of Get-AzureRmVM.
I pass $DeserializedJson to the deployment template' parameter (of type array).
For reference, the lovely error New-AzureRmResourceGroupDeployment throws is
"The template output '{output_name}' is not valid: The language expression property 'Microsoft.WindowsAzure.ResourceStack.Frontdoor.Expression.Expressions.JTokenExpression'
can't be evaluated.."
There is a way to deal with your situation. Leave most of you code as-is, just change the way to deal with the $serverIps object. This code can deal with $null, only one item, and many items.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
# Always use ".Count" instead of ".Length".
# This works on $null, only one item, or many items.
if ($serverIps.Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
# Always use foreach on a array-possible object, so that
# you don't have deal with this issue anymore.
$serverIps | foreach {
# The $serverIps could be $null. Even $null can loop once.
# So we need to skip the $null condition.
if ($_ -ne $null) {
# Get the index of the array.
# The #($serverIps) make sure it must be an array.
$idx = #($serverIps).IndexOf($item)
if ($idx -eq 0) { $primaryIp = $_ }
if ($idx -eq 1) { $secondaryIp = $_ }
}
}
In PowerShell Core, there is a .Count property exists on every objects. In Windows PowerShell, there are "almost" every object has an .Count property.

shortened output of property values in recursive function

I want to recursively enumerated all WMI namespaces.I have this function:
function Get-WmiNamespace {
Param(
[parameter()]
[string]$Namespace = 'root',
[parameter()]
[string]$Locale = 'MS_409',
[parameter()]
[switch]$Recurse
)
Begin {
$WMIParams = #{
Namespace = $Namespace
Class = '__NAMESPACE'
Locale = $Locale
ErrorAction = 'SilentlyContinue'
}
}
Process {
Get-WmiObject #WMIParams |
Sort-Object -Property Name -CaseSensitive -Culture "en-US" |
ForEach-Object {
$WMIParams.Namespace = "{0}\{1}" -f $_.__NAMESPACE, $_.Name
$object = [PSCustomObject] #{
Namespace = $WMIParams.Namespace
}
$object.PSTypeNames.Insert(0,'Wmi.Namespace.Name')
$object
if ($recurse) {
$PSBoundParameters.Namespace = $WMIParams.Namespace
Get-WMINamespace #PSBoundParameters
}
}
}
}
Inspired here:
[https://learn-powershell.net/2014/05/09/quick-hits-list-all-available-wmi-namespaces-using-powershell/]
I get this output:
Namespace
---------
ROOT\Appv
ROOT\CIMV2
ROOT\CIMV2\mdm
ROOT\CIMV2\mdm\dmmap
ROOT\CIMV2\mdm\MS_405
ROOT\CIMV2\ms_405
ROOT\CIMV2\ms_409
ROOT\CIMV2\power
ROOT\CIMV2\power\m...
ROOT\CIMV2\power\m...
ROOT\CIMV2\Security
ROOT\CIMV2\Securit...
ROOT\CIMV2\Securit...
ROOT\CIMV2\Termina...
ROOT\CIMV2\Termina...
ROOT\Cli
ROOT\Cli\MS_405
ROOT\Cli\MS_409
ROOT\DEFAULT
ROOT\DEFAULT\ms_405
ROOT\DEFAULT\ms_409
ROOT\directory
ROOT\directory\LDAP
ROOT\directory\LDA...
ROOT\directory\LDA...
ROOT\Hardware
ROOT\Hardware\ms_405
ROOT\Hardware\ms_409
ROOT\Intel_ME
ROOT\IntelNCS2
ROOT\IntelNCS2\ms_409
ROOT\Interop
ROOT\Interop\ms_405
ROOT\Interop\ms_409
ROOT\Microsoft
ROOT\Microsoft\Hom...
ROOT\Microsoft\pro...
ROOT\Microsoft\Sec...
ROOT\Microsoft\Uev
ROOT\Microsoft\Win...
...
ROOT\Microsoft\Win...
ROOT\msdtc
ROOT\PEH
ROOT\Policy
ROOT\Policy\ms_405
ROOT\Policy\ms_409
ROOT\RSOP
ROOT\RSOP\Computer
ROOT\RSOP\User
ROOT\SECURITY
ROOT\SecurityCenter
ROOT\SecurityCenter2
ROOT\ServiceModel
ROOT\StandardCimv2
ROOT\StandardCimv2...
ROOT\StandardCimv2...
ROOT\StandardCimv2...
ROOT\StandardCimv2...
ROOT\subscription
ROOT\subscription\...
ROOT\subscription\...
ROOT\WMI
ROOT\WMI\ms_405
ROOT\WMI\ms_409
Namespaces names are truncated.
I guess the reason is the width of Name column is set in first iteration of function Get-WmiNamespace according longest value (ROOT\SecurityCenter2).
It can be fixed by piping output to Format-Table with -AutoSize parameter:
Namespace
---------
ROOT\Appv
ROOT\CIMV2
ROOT\CIMV2\mdm
ROOT\CIMV2\mdm\dmmap
ROOT\CIMV2\mdm\MS_405
ROOT\CIMV2\ms_405
ROOT\CIMV2\ms_409
ROOT\CIMV2\power
ROOT\CIMV2\power\ms_405
ROOT\CIMV2\power\ms_409
ROOT\CIMV2\Security
ROOT\CIMV2\Security\MicrosoftTpm
ROOT\CIMV2\Security\MicrosoftVolumeEncryption
ROOT\CIMV2\TerminalServices
ROOT\CIMV2\TerminalServices\ms_405
ROOT\Cli
ROOT\Cli\MS_405
ROOT\Cli\MS_409
...
What would be the best way to solve this behavior?
The entire name is stored in the namespace property. If you just want to see it in the command output, you could use -ExpandProperty from Select-Object
Get-WmiNamespace -Recurse | select -ExpandProperty namespace
Also, just adding Sort shows the full name
Get-WmiNamespace -Recurse | Sort
Without using Expand and Doug is points out, you can just dot it and avoid the format stuff or futzing with trying to change the function itself, etc. Well, at least for a single column.
(Get-WmiNamespace -Recurse).Namespace
# Results
<#
(Get-WmiNamespace -Recurse).Namespace
ROOT\Appv
...
ROOT\CIMV2\Security\MicrosoftTpm
ROOT\CIMV2\Security\MicrosoftVolumeEncryption
ROOT\CIMV2\TerminalServices
ROOT\CIMV2\TerminalServices\ms_409
...
ROOT\Microsoft\SqlServer\ComputerManagement15
ROOT\Microsoft\SqlServer\ComputerManagement15\MS_409
ROOT\Microsoft\SqlServer\ServerEvents
ROOT\Microsoft\SqlServer\ServerEvents\MSSQLSERVER
...
ROOT\Microsoft\Windows\DesiredStateConfigurationProxy
ROOT\Microsoft\Windows\DesiredStateConfigurationProxy\MS_409
...
#>
If you saying you want, like a Linux column file list of these then other steps are needed You can do this Linux like multi-column using the Format-Wide cmdlet.
For Example:
Get-ChildItem -Path 'C:\Program Files' -Recurse |
Format-Wide -Property Name
Get-ChildItem -Path 'C:\Program Files' -Recurse |
Format-Wide -Property Name -Column 5
So, for this list to get a table-like view, do the same thing.
Get-WmiNamespace -Recurse |
Format-Wide -Property namespace -Column 3
# Results
<#
ROOT\Appv ROOT\aspnet ROOT\CIMV2
ROOT\CIMV2\mdm ROOT\CIMV2\mdm\dmmap ROOT\CIMV2\mdm\MS_409
ROOT\CIMV2\ms_409 ROOT\CIMV2\NV ROOT\CIMV2\NV\Events
....
#>
Use whatever column count that fits your screen. No, you cannot use columns and autosize together as they are mutually exclusive.
So, as Doug points out you can make your own formatter, or as per your comment...
'My point was whether there is a way to modify function itself to get
table formated '
...you can doctor the function to use aforementioned for the results you are after.

Compare-object output in variable

I'm building a script that will compare the last octed of in-use IPv4 addreses, with all the available octeds (2 till 254).
I am already this far that I do get a result by comparing array's, but my end-result is also an array, and I cannot seem to get only the number.
My script:
$guestIP = #("192.168.31.200","192.168.31.31","192.168.31.90","192.168.31.25","192.168.31.100")
$AllLastOcted = $("2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63","64","65","66","67","68","69","70","71","72","73","74","75","76","77","78","79","80","81","82","83","84","85","86","87","88","89","90","91","92","93","94","95","96","97","98","99","100","101","102","103","104","105","106","107","108","109","110","111","112","113","114","115","116","117","118","119","120","121","122","123","124","125","126","127","128","129","130","131","132","133","134","135","136","137","138","139","140","141","142","143","144","145","146","147","148","149","150","151","152","153","154","155","156","157","158","159","160","161","162","163","164","165","166","167","168","169","170","171","172","173","174","175","176","177","178","179","180","181","182","183","184","185","186","187","188","189","190","191","192","193","194","195","196","197","198","199","200","201","202","203","204","205","206","207","208","209","210","211","212","213","214","215","216","217","218","219","220","221","222","223","224","225","226","227","228","229","230","231","232","233","234","235","236","237","238","239","240","241","242","243","244","245","246","247","248","249","250","251","252","253","254")
$guestIP = $guestIP | sort -Property {$_-replace '[\d]'},{$_-replace '[a-zA-Z\p{P}]'-as [int]}
$AllLastOcted = $AllLastOcted | sort -Property {$_-replace '[\d]'},{$_-replace '[a-zA-Z\p{P}]'-as [int]}
$guestIP = $guestIP -replace('192.168.31.','')
Compare-Object -ReferenceObject ($AllLastOcted ) -DifferenceObject ($guestIP) | select -Property InputObject -First 1 | set-Variable AvailableOcted -PassThru
$AvailableOcted
My goal is, is that I have as result the first-available octed that I can use.
like:
write-host "IP that can be used is 192.168.31.$AvailableOcted"
PS > IP that can used is 192.168.31.2
You can simplify this a lot.
Instead of defining all numbers from 2 to 254 you can use the range operator to create the array. You also don't need the [int] casts. Instead of using the Compare-Object cmdlet to filter the octeds, you can use the Where-Object cmdlet:
$guestIP = #("192.168.31.200","192.168.31.31","192.168.31.90","192.168.31.25","192.168.31.100")
$AllLastOcted = 2 .. 254
$usedOcted = $guestIP -replace '.*\.'
$nextAvailableOcted = $AllLastOcted | Where { $_ -NotIn $usedOcted } | select -first 1
write-host "IP that can be used is 192.168.31.$nextAvailableOcted"
Output:
IP that can be used is 192.168.31.2
Well, as simple as:
$AvailableOcted.InputObject
would return only the value.
So it would look like this:
write-host ("IP that can be used is 192.168.31." + $AvailableOcted.InputObject)

How to get the value of a particular propery from the result of a powershell command

I have a variable $ results which has the value :
SESSIONNAME USERNAME ID STATE TYPE DEVICE
rdp-tcp#1 account17 7 Active rdpwd
I want to get the value of ID alone and use it in a different query.
I tried the following ways :
1.$idValue = #($result | %{ $_.ID }) - but it was not getting the value.
2.$result |Select -ExpandProperty ID - I was getting the error 'Select-Object : Property "ID" cannot be found.'
How to get the value of the property ID alone from the result?
The output of the qwinsta/query commands are strings, not objects, so there isn't a property ID to print. You need to transform the strings into objects if you want the fields as properties:
query session | ? { $_ -match '^[ >](\S+) +(\S*?) +(\d+) +(\S+)' } |
select #{n='Service';e={$matches[1]}},
#{n='Username';e={$matches[2]}},
#{n='ID';e={$matches[3]}},
#{n='Status';e={$matches[4]}} | % {
$_.ID
}
Or, if you're just interested in the ID, you could do a regular expression replacement like this:
$account = 'account17'
$pattern = '^[ >]\S+ +\S*? +(\d+) +\S+.*'
(query session $account | select -Skip 1) -replace $pattern, '$1'
This is the format to refer to a single property properly. I don't see your command to create your RDP $result, so I'll example get-process, encapsulate it with () and tack an ().ID to the end. Works with any property, not just.ID
(get-process | where {$_.Name -eq "Powershell"}|select ID).ID
# or
$MYID = (get-process | where {$_.Name -eq "Powershell"}|select ID).ID
$MYID
Another option is -split:
One solution, using V4:
($result).ForEach({($_ -split '\s+')[2]}) -match '\d'

How can I force Powershell to return an array when a call only returns one object?

I'm using Powershell to set up IIS bindings on a web server, and having a problem with the following code:
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if ($serverIps.length -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
$primaryIp = $serverIps[0]
$secondaryIp = $serverIps[1]
If there's 2+ IPs on the server, fine - Powershell returns an array, and I can query the array length and extract the first and second addresses just fine.
Problem is - if there's only one IP, Powershell doesn't return a one-element array, it returns the IP address (as a string, like "192.168.0.100") - the string has a .length property, it's greater than 1, so the test passes, and I end up with the first two characters in the string, instead of the first two IP addresses in the collection.
How can I either force Powershell to return a one-element collection, or alternatively determine whether the returned "thing" is an object rather than a collection?
Define the variable as an array in one of two ways...
Wrap your piped commands in parentheses with an # at the beginning:
$serverIps = #(gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort)
Specify the data type of the variable as an array:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
Or, check the data type of the variable...
IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
Force the result to an Array so you could have a Count property. Single objects (scalar) do not have a Count property. Strings have a length property so you might get false results, use the Count property:
if (#($serverIps).Count -le 1)...
By the way, instead of using a wildcard that can also match strings, use the -as operator:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
You can either add a comma(,) before return list like return ,$list or cast it [Array] or [YourType[]] at where you tend to use the list.
If you declare the variable as an array ahead of time, you can add elements to it - even if it is just one...
This should work...
$serverIps = #()
gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort | ForEach-Object{$serverIps += $_}
You can use Measure-Object to get the actual object count, without resorting to an object's Count property.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if (($serverIps | Measure).Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
Return as a referenced object, so it never converted while passing.
return #{ Value = #("single data") }
I had this problem passing an array to an Azure deployment template. If there was one object, PowerShell "converted" it to a string. In the example below, $a is returned from a function that gets VM objected according to the value of a tag. I pass the $a to the New-AzureRmResourceGroupDeployment cmdlet by wrapping it in #(). Like so:
$TemplateParameterObject=#{
VMObject=#($a)
}
New-AzureRmResourceGroupDeployment -ResourceGroupName $RG -Name "TestVmByRole" -Mode Incremental -DeploymentDebugLogLevel All -TemplateFile $templatePath -TemplateParameterObject $TemplateParameterObject -verbose
VMObject is one of the template's parameters.
Might not be the most technical / robust way to do it, but it's enough for Azure.
Update
Well the above did work. I've tried all the above and some, but the only way I have managed to pass $vmObject as an array, compatible with the deployment template, with one element is as follows (I expect MS have been playing again (this was a report and fixed bug in 2015)):
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
foreach($vmObject in $vmObjects)
{
#$vmTemplateObject = $vmObject
$asJson = (ConvertTo-Json -InputObject $vmObject -Depth 10 -Verbose) #-replace '\s',''
$DeserializedJson = (New-Object -TypeName System.Web.Script.Serialization.JavaScriptSerializer -Property #{MaxJsonLength=67108864}).DeserializeObject($asJson)
}
$vmObjects is the output of Get-AzureRmVM.
I pass $DeserializedJson to the deployment template' parameter (of type array).
For reference, the lovely error New-AzureRmResourceGroupDeployment throws is
"The template output '{output_name}' is not valid: The language expression property 'Microsoft.WindowsAzure.ResourceStack.Frontdoor.Expression.Expressions.JTokenExpression'
can't be evaluated.."
There is a way to deal with your situation. Leave most of you code as-is, just change the way to deal with the $serverIps object. This code can deal with $null, only one item, and many items.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
# Always use ".Count" instead of ".Length".
# This works on $null, only one item, or many items.
if ($serverIps.Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
# Always use foreach on a array-possible object, so that
# you don't have deal with this issue anymore.
$serverIps | foreach {
# The $serverIps could be $null. Even $null can loop once.
# So we need to skip the $null condition.
if ($_ -ne $null) {
# Get the index of the array.
# The #($serverIps) make sure it must be an array.
$idx = #($serverIps).IndexOf($item)
if ($idx -eq 0) { $primaryIp = $_ }
if ($idx -eq 1) { $secondaryIp = $_ }
}
}
In PowerShell Core, there is a .Count property exists on every objects. In Windows PowerShell, there are "almost" every object has an .Count property.