Why Powershell output all items in collection ignorig IF statement? - windows

This code supposed to output only macaddress of active connection, yet it output all macaddresses of all PhysicalAdapters
# Assign info on Network Connections selected fileds to collection
$q = Get-CIMInstance Win32_NetworkAdapter | Select-Object NetConnectionStatus, PhysicalAdapter, MACAddress
# Find mac address of active connection
foreach ($i in $q) {IF ($q.NetConnectionStatus -eq 2) {Write-Host $i.MacAddress}}
In output I recieve all macaddresses of all physical adapters.

You have a typo on your if condition, $q is the collection not the item ($i):
IF ($q.NetConnectionStatus -eq 2)
Since -eq can act as a filter when the left-hand side of the operation, as long as there is at least one element in $q.NetConnectionStatus equal to 2, the condition will be $true hence all items are being outputted to the console.
Try this simple example to understand what is happening:
$collection = 0..10 | ForEach-Object {
[pscustomobject]#{
NetConnectionStatus = $_
MACAddress = Get-Random
}
}
foreach($item in $collection) {
if($collection.NetConnectionStatus -eq 2) {
$item.MACAddress
}
}
This will output 11 random numbers to your console. The right filtering condition on above example should have been:
if($item.NetConnectionStatus -eq 2) {

Related

Powershell Plus or Minus Comparison Operator (Fuzzy Logic)?

So let me tell you what I'm trying to do here. Our SolarWinds alerts report on disk capacity as read by Windows, not the Virtual Machine vDisk size setting. What I'm trying to do is match the size so that I can find the correct vDisk and report on its datastore free space to determine whether or not we can add more.
Here's the problem, the GB number never matches between Windows and VMWare. Say the disk has a 149.67 capacity as reported by Windows, well the VMWare setting is 150, or 150.18854, or anything of that sort. I cannot find the vdisk without knowing the exact number, but theoretically I could find it if I could just say, have a comparison operator that had some breathing room, like plus or minus 1 or even 0.5. So for example:
Get-HardDisk -Vm SERVERNAME | Where-Object {
$_.CapacityGB -lt $size + 0.5 -and
$_.CapacityGB -gt $size - 0.5
}
This doesn't work though, for whatever reason. I need something similar to this. Any ideas?
UPDATE: Turns out to be user error, I was experimenting with the wrong number when testing the command. I thought it was the syntax, it was the number I was using itself.
So because I managed to answer my own question I thought I'd post a script for achieving this here. Note that you will need to have a txt file with a comma separated servername and capacity. You could probably modify this to do many other things with VMWare data gathering if you wanted. In the end you'll need to know which columns are which and import to Excel as comma delimited.
Most the variables are decimal values.
Also note that I have no yet figured out a way to programatically deal with the discovery of multiple matching disks.
$serverlist = Get-Content "./ServerList.txt"
$logfile = "./Stores.txt"
remove-item "./Stores.txt"
Function LogWrite {
Param (
[string]$srv,
[string]$disk,
[string]$store
)
Add-Content $logfile -value $srv","$disk","$store
}
foreach ($item in $serverlist){
$store = "Blank"
$disk = "Blank"
try {
$server,$arg = $item.split(',')
$round = [math]::Round($arg,0)
$disk = get-harddisk -vm $server | where-object{$_.CapacityGB -lt ($round + 2) -and $_.CapacityGB -gt ($round - 2) }
if ([string]::IsNullOrEmpty($disk)){
$disk = "Problem locating disk."
$store = "N/A"
continue
}
if ($disk.count -gt 1) {
$disk = "More than one matching disk."
$store = "N/A"
} else {
$store = get-harddisk -vm $server | where-object{$_.CapacityGB -lt ($round + 2) -and $_.CapacityGB -gt ($round - 2) } | Get-Datastore | %{ "{0},{1},{2}" -f $_.Name,[math]::Round($_.FreeSpaceGB,1),[math]::Round($_.CapacityGB,1) }
}
}
catch {
$disk = "Physical"
$store = "N/A"
}
LogWrite $server $disk $store
}

How to get NUMA difference for a specific NIC which uses RSS in Powershell

I want to write a powershell-script which checks if a network interface card which uses receive side scaling uses a processor with a NUMA (Non-Uniform Memory Access) distance > 0.
What I've done so far:
$name = "Ethernet"
$adapter = Get-NetAdapterRss -Name $name
This outputs the RSS-Adapter processor data (together with other information) like:
RssProcessorArray: [Group:Number/NUMA Distance] : 0:0/0 0:2/0 0:4/0 0:6/0 0:8/0 0:10/0 0:12/0 0:14/0
0:16/0 0:18/0 0:20/0 0:22/0 0:24/32767 0:26/32767 0:28/32767
0:30/32767
0:32/32767 0:34/32767 0:36/32767 0:38/32767 0:40/32767
0:42/32767 0:44/32767 0:46/32767
As you see, the NUMA distance is the value behind the '/'.
Now i want to retrieve it like:
foreach($processor in $adapter.RssProcessorArray)
{
Write-Host $processor.ProcessorGroup
Write-Host $processor.ProcessorNumber
Write-Host $processor.??
}
Somehow there is no ".NumaDistance" property on the object i get. How can i get this value for each processor in the list?
Similar idea, but with regexp:
$str = (Get-NetAdapterrss -name "Ethernet" | Out-String).Split("`n") | where {$_ -like 'RssProcessorArray*'}
$rss = $str | Select-String '\d+:\d+/\d+' -AllMatches
Write-Output $rss.Matches.Value
$rss.Matches.Value | foreach { ($_ -split "[:/]") -join "---" } #if need each value separetly
Using static data as an example, but hope this helps
$text = 'RssProcessorArray: [Group:Number/NUMA Distance] : 0:0/0 0:2/0 0:4/0 0:6/0 0:8/0 0:10/0 0:12/0 0:14/0 0:16/0 0:18/0 0:20/0 0:22/0 0:24/32767 0:26/32767 0:28/32767 0:30/32767 0:32/32767 0:34/32767 0:36/32767 0:38/32767 0:40/32767 0:42/32767 0:44/32767 0:46/32767'
# split the text up on spaces
$firstSplit = $text.Split(' ')
# take all results starting at the first 0:0/0
# put into an array
[array]$processData = $firstSplit[4..($firstSplit.Count -1)]
# get just the data after the / for each item in the array
[array]$splitProcessData = $processData.split('/') | ? {$_ -notmatch ':'}
foreach($processor in $adapter.RssProcessorArray)
{
Write-Host $processor.ProcessorGroup
Write-Host $processor.ProcessorNumber
foreach($entry in $splitProcessData)
{
Write-Host $entry
}
}

Powershell Format-Wide and Sorting

One of the things I like to do in Powershell is:
Set-Location 'C:\Windows\System32\WindowsPowerShell\v1.0\en-US\'
Get-ChildItem *.txt | Format-Wide -Column 3
This gives me a great view on everything there is to learn and explore. The thing that bothered me is the sorting, because now I have 3 columns that start with 'A'. It would be more readable when I'd have (for example) one column with A-J, one column L-R, and one going from R-Z. This bothered me so much, I wrote a function to do it:
Function PSHELP {
Set-Location 'C:\Windows\System32\WindowsPowerShell\v1.0\en-US\'
#Initialize variables
$files = gci *.txt | select name
$count = $files.count
$remainder = $count % 3
$rows = ($count - $remainder) /3 -as [int]
#I add an extra row by default, to deal with remainders
$rows++
$Table = New-Object 'object[,]' $rows,3
#Build up a table the way I want to see it
#column 1: A,B,C...
#column 2: L,M,N..
#column 3: R,...,Z
$index = 0
for ($j = 0; $j -lt 3; $j++)
{
$ThisColumnLength = $rows
if($j -ge $remainder){
$ThisColumnLength--
}
for ($i = 0; $i -lt $ThisColumnLength; $i++)
{
$table[$i,$j] = $files[$index]
$index++
}
}
#Read the table in the order Format-Wide throws them on the screen
#And store this in an array
$array = #()
for ($i = 0; $i -lt $rows; $i++)
{ $ThisRowLength = 3
if(($i+1) -eq $Rows){
$ThisRowLength = $remainder
}
if ($ThisRowLength -gt 0){
for ($j = 0; $j -lt $ThisRowLength; $j++)
{
$array += $table[$i,$j]
}
}
}
$array | fw -Column 3
}
Is there a more 'standard' way to do this in powershell? It seems like quite a natural option to me, but I couldn't find it. Is there an option or command that I've missed?
To clarify: I am not looking for ways to find help. This question is about the Format-Wide command, and/or possible alternative. I just thought this would be a nice example.
[Edit:] Changed my function to something slightly less clumbsy.
[Edit2:] The code I posted is flawed, and it's getting late. If you paste it in the shell and compare it with {Get-Childitem *.txt | format-wide -column 3}', you should be able to see what I am trying to do here. I hope somebody can suggest some kind of alternative.
[Edit3:] Modified the code again, finally got this to work. In the process I found out a very interesting thing about what Format-Wide returns:
PS> (Get-ChildItem).count
Result: 125
PS> (Get-ChildItem | Format-Wide).count
Result: 129
This confused me a lot, because sometimes I counted the results and didn't get what I expected, so a couple of times I thought something was wrong with my code, but maybe everything was fine.
I found something that does exactly what I want, and is more generic:
http://www.leporelo.eu/blog.aspx?id=powershell-formatting-format-wide-rotated-to-format-columns (dead link)
Version 0.4 code (pasted at the bottom of this answer in case this link dies too)
Archived 0.1 code (included because it has more discussion/examples)
Example with regular Format-Wide
'The basic promise behind implicit remoting is that you can work ' +
'with remote commands using local syntax.' -split ' ' | Format-Wide -force -prop {$_} -col 3
The basic promise
behind implicit remoting
is that you
can work with
remote commands using
local syntax.
Example with Format-Columns
'The basic promise behind implicit remoting is that you can work ' +
'with remote commands using local syntax.' -split ' ' | . Format-Columns -col 3
The is remote
basic that commands
promise you using
behind can local
implicit work syntax.
remoting with
I am still surprised Powershell doesn't have a built-in option to do this.
Version 0.4 code:
function Format-Columns {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[PsObject[]]$InputObject,
[Object]$Property,
[int]$Column,
[int]$MaxColumn,
[switch]$Autosize
)
Begin { $values = #() }
Process { $values += $InputObject }
End {
function ProcessValues {
$ret = $values
$p = $Property
if ($p -is [Hashtable]) {
$exp = $p.Expression
if ($exp) {
if ($exp -is [string]) { $ret = $ret | % { $_.($exp) } }
elseif ($exp -is [scriptblock]) { $ret = $ret | % { & $exp $_} }
else { throw 'Invalid Expression value' }
}
if ($p.FormatString) {
if ($p.FormatString -is [string]) { $ret = $ret | % { $p.FormatString -f $_ } }
else { throw 'Invalid format string' }
}
}
elseif ($p -is [scriptblock]) { $ret = $ret | % { & $p $_} }
elseif ($p -is [string]) { $ret = $ret | % { $_.$p } }
elseif ($p -ne $null) { throw 'Invalid -property type' }
# in case there were some numbers, objects, etc., convert them to string
$ret | % { $_.ToString() }
}
if (!$Column) { $Autosize = $true }
$values = ProcessValues
$valuesCount = #($values).Count
if ($valuesCount -eq 1) {
return $values
}
# from some reason the console host doesn't use the last column and writes to new line
$consoleWidth = $host.ui.RawUI.maxWindowSize.Width - 1
$gutterWidth = 2
# get length of the longest string
$values | % -Begin { [int]$maxLength = -1 } -Process { $maxLength = [Math]::Max($maxLength,$_.Length) }
# get count of columns if not provided
if ($Autosize) {
$Column = [Math]::Max( 1, ([Math]::Floor(($consoleWidth/($maxLength+$gutterWidth)))) )
$remainingSpace = $consoleWidth - $Column*($maxLength+$gutterWidth);
if ($remainingSpace -ge $maxLength) {
$Column++
}
if ($MaxColumn -and $MaxColumn -lt $Column) {
$Column = $MaxColumn
}
}
$countOfRows = [Math]::Ceiling($valuesCount / $Column)
$maxPossibleLength = [Math]::Floor( ($consoleWidth / $Column) )
# cut too long values, considers count of columns and space between them
$values = $values | % {
if ($_.length -gt $maxPossibleLength) { $_.Remove($maxPossibleLength-2) + '..' }
else { $_ }
}
#add empty values so that the values fill rectangle (2 dim array) without space
if ($Column -gt 1) {
$values += (#('') * ($countOfRows*$Column - $valuesCount))
}
# in case there is only one item, make it array
$values = #($values)
<#
now we have values like this: 1, 2, 3, 4, 5, 6, 7, ''
and we want to display them like this:
1 3 5 7
2 4 6 ''
#>
$formatString = (1..$Column | %{ "{$($_-1),-$maxPossibleLength}" }) -join ''
1..$countOfRows | % {
$r = $_-1
$line = #(1..$Column | %{ $values[$r + ($_-1)*$countOfRows]} )
Write-Output "$($formatString -f $line)".PadRight($consoleWidth,' ')
}
}
<#
.SYNOPSIS
Formats incoming data to columns.
.DESCRIPTION
It works similarly as Format-Wide but it works vertically. Format-Wide outputs the data row by row, but Format-Columns outputs them column by column.
.PARAMETER Property
Name of property to get from the object.
It may be
-- string - name of property.
-- scriptblock
-- hashtable with keys 'Expression' (value is string=property name or scriptblock)
and 'FormatString' (used in -f operator)
.PARAMETER Column
Count of columns
.PARAMETER Autosize
Determines if count of columns is computed automatically.
.PARAMETER MaxColumn
Maximal count of columns if Autosize is specified
.PARAMETER InputObject
Data to display
.EXAMPLE
1..150 | Format-Columns -Autosize
.EXAMPLE
Format-Columns -Col 3 -Input 1..130
.EXAMPLE
Get-Process | Format-Columns -prop #{Expression='Handles'; FormatString='{0:00000}'} -auto
.EXAMPLE
Get-Process | Format-Columns -prop {$_.Handles} -auto
.NOTES
Name: Get-Columns
Author: stej, http://twitter.com/stejcz
Site: http://www.leporelo.eu/blog.aspx?id=powershell-formatting-format-wide-rotated-to-format-columns
Lastedit: 2017-09-11
Version 0.4 - 2017-09-11
- removed color support and changed output from Write-Host to Write-Output
Version 0.3 - 2017-04-24
- added ForegroundColor and BackgroundColor
Version 0.2 - 2010-01-14
- added MaxColumn
- fixed bug - displaying collection of 1 item was incorrect
Version 0.1 - 2010-01-06
#>
}
If you mean standard way of finding all the help files in PowerShell, then yes there:
Get-Help * -Category HelpFile
Outside of that I just go check this page on Technet: Windows PowerShell Core About Topics
Here is a fork of the above script in a module form: https://github.com/loxia01/FormatColumn
Function syntax:
Format-Column [[-Property] <Object>] [-MaxColumnCount <int>] [-MinRowCount <int>] [-OrderBy <string>] [-InputObject <psobject>] [<CommonParameters>]
Format-Column [[-Property] <Object>] -ColumnCount <int> [-OrderBy <string>] [-InputObject <psobject>] [<CommonParameters>]

PowerShell's equivalent of LINQ All, or how can I verify all the items in the collection are equal to specific value?

I have a collection of items, which I build from a regex match, like this:
$collection = $input | foreach {
if ($_ -match $regex) {$matches} else { return }
} |
Select-Object –Property #{name='command'; expression={$_.command} },
#{name='id'; expression={$_.id} }
(sorry if that's not the best way to do this, I'm learning PowerShell :))
What I'd like to do is to make sure all the command properties in this $collection are equal to the same command, e.g. "myCommand", how can I do that?
ff this were C#, I'd probably do something like:
if (collection.All(item => item.Key == "myCommand")) { ... }
What's the idiomatic way to do this in PowerShell?
The following snippet returns $true when all items in array are equal. What it does is:
iterate through an array of objects using ForEach-Object
compare each one with the previous using the Compare-Object cmdlet.
If Compare-Object returns non-null at least once, which means that the two objects being compared are different, the snippet will return $false, otherwise $true.
Applying the snippet to the array #(1,1,1,1,1,2) will return $false because of the last item.
#(1,1,1,1,1,2) |
ForEach-Object -Begin { $last = $null; $result = $true } {
if ($last -ne $null -and $result -and (Compare-Object $last $_) -ne $null) {
$result = $false
}
$last = $_
} -End { $result }
I was pointed to this LINQ Module for PowerShell, which allowed me to do simply:
$collection | Linq-All { $_.command -eq "myCommand" }
Which is just what I needed! More examples here.

Active Directory and Powershell

I'm currently rewriting a script that is in VB into a Powershell script.
What the script does is search our Active Directory for a user based on the script-users input.
Function PromptForName{
$nameInput = "*"
$nameInput += Read-Host ("Please enter a full or partial name.")
$nameInput += "*"
return $nameInput
}
Function FindUsers{
param ([string]$n)
$usersArray = Get-ADUser -f {DisplayName -like $n} | Select-Object Name
return $usersArray
}
This code will print out the correct list of names. What I then want to do is allow the user to choose one of those names, and have more information about that person. I'm stuck at allowing the script-user to select one of those names.
How can I prompt for another input; where the box will display a numbered list of all the names that FindUsers gave, and then return a number based on which user they chose? I'm completely lost.
This is currently how I am trying to do it, although I'm pretty sure it's completely wrong.
Function PrintUsers{
param $a
[int]$i, $j
[string]$userList
$j = 1
foreach($object in $array){
$userList += ($j + $array[$i])
$j++
}
return $userList
}
Function SelectUser{
param $list
$user = Read-Host ($list)
}
EDIT:
I have updated my functions to the following:
Function FindUsers{
param ([string]$n)
$usersArray = #(Get-ADUser -f {DisplayName -like $n} | Select-Object Name| Format-List)
return $usersArray
}
Function PrintUsers{
param ([String[]]$array)
$i
for($i = 1; $i -lt $usersArray.length; $i++){
Write-Host "$i. $($usersArray[$i-1].Name)"
}
}
The output after FindUsers is like this:
Name : xxxxx yyyyy
Name : xxxxx zzzzz
etc.
So the return of $usersArray is printing all that.
I don't want any printing until the PrintUsers function, and I want it to be in a numbered list type format like this:
1. xxxx yyyy
2. xxxx zzzz
etc.
I'm having the most difficult time figuring this out.
# get all users
$usersArray = #(Get-ADUser -f {DisplayName -like $n} )
# create menu
for($i=1; $i -le $usersArray.count; $i++){
Write-Host "$i. $($usersArray[$i-1].Name)"
}
# prompt for user number
$user = Read-Host Enter the user number to get more info
# display full info for selected user
$usersArray[$user-1] | Format-List *
Use Add-Member to add a unique identifier to each user. Let's treat processes as if they're user objects, for the sake of example:
$procs = gps;
$procs = $procs | % { $i=0; } {
Add-Member -MemberType NoteProperty -InputObject $_ -Name Number -Value $i -PassThru;
$i++;
};
$procs | select Number,Name;
$procid = Read-Host -Prompt 'Enter the number of the process you would like to operate on';
$proc = $procs | ? { $_.Number -eq $procid };
Write-Host -Object ('Operating on proc: {0}' -f $proc.Name);

Resources