Select-Object changes selected value - windows

Short problem description:
I'm trying to get the path of a network drive in PowerShell. With Get-PSDrive I get all the information I need.
PS X:\> Get-PSDrive -Name X
Name Used (GB) Free (GB) Provider Root CurrentLocation
---- --------- --------- -------- ---- ---------------
X 1527,59 214,61 FileSystem \\Path\to\network\drive
Now I want to save Root in a variable, but when I do
PS X:\> Get-PSDrive -Name X | Select-Object Root
I get
X:\
Why does Select-Object change the value? How can I get the "raw" value?

The default table display of Get-PSDrive displays the DisplayRoot property value as Root unless it is empty. You can see this behavior by piping your Get-PSDrive to Format-List *. To control the output, you can use a calculated property with Select-Object.
Get-PSDrive -Name X | Select-Object #{
Name='Root'
Expression = {
if ([string]::IsNullOrEmpty($_.DisplayRoot)) {
$_.Root
}
else {
$_.DisplayRoot
}
}
}
Explanation:
If you check the $pshome\PowerShellCore.format.ps1xml file, it defines the root value with the following expression, which is why you see this behavior.
if($_.DisplayRoot -ne $null) { $_.DisplayRoot } else { $_.Root }

Related

match one of two pid's using the processes full path

I have two identical running processes called RocketLeague.exe
I want to store one of the processes PID's in a variable for later use in another command by matching both processes full file paths.
I have been able to come up with two commands so far that pipe the full path of the processes but can't figure out how to continue the piping of the right PID into a final custom command.
How can I store the correct PID in a variable for use in my custom command?
1) Get-Process -Name 'RocketLeague' | Format-List -Property Path
2) Get-Process -Name 'RocketLeague'
Using the feedback from user:lit I was able to come up with this solution.
$procID = Get-process -Name 'RocketLeague' | Select-Object -Property Id,Path | ForEach-Object {
If($_.Path -eq 'C:\Program Files (x86)\Steam\steamapps\common\rocketleague\Binaries\Win64\RocketLeague.exe'){
Set-Variable -Name 'procSteam' -Value $_.Id; Write-Host $procSteam
}
}
If you just want the specific Process that is equal to that Path you can use Where-Object or .Where() method for filtering. The code would be reduced to:
# This:
$procID = (Get-Process -Name 'RocketLeague').Where({
$_.Path -eq 'C:\Program Files (x86)\Steam\steamapps\common\rocketleague\Binaries\Win64\RocketLeague.exe'
}) | Select-Object -Property Id, Path
# Or this:
$procID = Get-Process -Name 'RocketLeague' | Where-Object {
$_.Path -eq 'C:\Program Files (x86)\Steam\steamapps\common\rocketleague\Binaries\Win64\RocketLeague.exe'
} | Select-Object -Property Id, Path
And if, for example there is only one of the paths that ends with Win64\....exe you can use .EndsWith() method:
$procID = (Get-Process -Name 'RocketLeague').Where({
([string]$_.Path).EndsWith('\Win64\RocketLeague.exe')
}) | Select-Object -Property Id, Path

Save the powershell's output in a CSV file

I have a powershell script in which i can get information about my operating system (windows version, build...). However all that information is shown in the powershell console and I want them to be exported to a CSV or a XML file.
The script is :
Get-CimInstance Win32_OperatingSystem |
Select-Object Caption, CSDVersion, ServicePackMajorVersion, BuildNumber |
FL
Use Export-Csv cmdlet:
Get-CimInstance Win32_OperatingSystem | Select-Object Caption, CSDVersion, ServicePackMajorVersion, BuildNumber | Export-Csv -NoTypeInformation -Path .\OS_Info.csv
Result (OS_Info.csv):
"Caption","CSDVersion","ServicePackMajorVersion","BuildNumber"
"Microsoft Windows Server 2012 R2 Datacenter",,"0","9600"
Thank you it worked, the file is generated in the folder System32
As Rohin Sidharth mentioned, .\ prefix for the path will create file in the current dir ($PWD in PowerShell). You probably run PowerShell as administrator: in this case the default directory is %WinDir%\System32. Just use full path or GetFolderPath .Net method to get common folder path, like desktop:
... | Export-Csv -NoTypeInformation -Path 'C:\OS_Info.csv'
... | Export-Csv -NoTypeInformation -Path (Join-Path -Path [System.Environment]::GetFolderPath('Desktop') -ChildPath 'OS_Info.csv')
Can you also show me how to export many results in the same file ? for
example i have a script in which i can know all the update that are
installed :
Get-Hotfix | Select HotfixID,Description,InstalledOn | Sort InstalledOnfunction
and i want the results saves in the same CSV file
You can do this by using Select-Object's calculated properties:
# Get OS info
$OsInfo = Get-CimInstance -ClassName Win32_OperatingSystem
Get-Hotfix | # Get HotFixes
Sort-Object -Property InstalledOnfunction | # Sort them
Select-Object -Property #( # Select required fields
# Add Caption property from $OsInfo variable
#{
Name = 'Caption'
Expression = {$OsInfo.Caption}
}
# Add CSDVersion property from $OsInfo variable
#{
Name = 'CSDVersion'
Expression = {$OsInfo.CSDVersion}
}
# Add ServicePackMajorVersion property from $OsInfo variable
#{
Name = 'ServicePackMajorVersion'
Expression = {$OsInfo.ServicePackMajorVersion}
}
# Add BuildNumber property from $OsInfo variable
#{
Name = 'BuildNumber'
Expression = {$OsInfo.BuildNumber}
}
# Add other properties from original HotFix object
'HotfixID'
'Description'
'InstalledOn'
) | Export-Csv -NoTypeInformation -Path 'C:\OS_Info.csv'
You can also try to join objects using custom function.
Quick tip: Make sure you don't pipe to Format List (FL) then pipe to export-csv or you'll open the CSV file and your data will look like this.
ClassId2e4f51ef21dd47e99d3c952918aff9cd pageHeaderEntry pageFooterEntry
autosizeInfo shapeInfo groupingEntry
033ecb2bc07a4d43b5ef94ed5a35d280
Microsoft.PowerShell.Commands.Internal.Format.ListViewHeaderInfo
9e210fe47d09416682b841769c78b8a3
27c87ef9bbda4f709f6b4002fa4af63c
4ec4f0187cb04f4cb6973460dfe252df
cf522b78d86c486691226b40aa69e95c

Select directory from a file

I need my program to give me every folder containing files which are out of the Windows' number of characters limit. It means if a file has more than 260 characters (248 for folders), I need it to write the address of the file's parent. And I need it to write it only once. For now, I'm using this code:
$maxLength = 248
Get-ChildItem $newPath -Recurse |
Where-Object { ($_.FullName.Length -gt $maxLength) } |
Select-Object -ExpandProperty FullName |
Split-Path $_.FullName
But the Split-Path won't work (this is the first time I use it). It tells me the -Path parameter has a null value (I can write -Path but it doesn't change anything).
If you want an example of what I need: imagine folder3 has a 230-character address and file.txt has a 280-character address:
C:\users\folder1\folder2\folder3\file.txt
Would write:
C:\users\folder1\folder2\folder3
I'm using PS2, by the way.
Spoiler: the tool you are building may not be able to report paths over the limit since Get-ChildItem cannot access them. You can try nevertheless, and also find other solutions in the links at the bottom.
Issue in your code: $_ only works in specific contexts, for example a ForEach-Object loop.
But here, at the end of the pipeline, you're only left with a string containing the full path (not the complete file object any more), so directly passing it to Split-Path should work:
$maxLength = 248
Get-ChildItem $newPath -Recurse |
Where-Object { ($_.FullName.Length -gt $maxLength) } |
Select-Object -ExpandProperty FullName |
Split-Path
as "C:\Windows\System32\regedt32.exe" | Split-Path would output C:\Windows\System32
Sidenote: what do (Get-Item C:\Windows\System32\regedt32.exe).DirectoryName and (Get-Item C:\Windows\System32\regedt32.exe).Directory.FullName output on your computer ? These both show the directory on my system.
Adapted code example:
$maxLength = 248
Get-ChildItem $newPath -Recurse |
Where-Object { ($_.FullName.Length -gt $maxLength) } |
ForEach-Object { $_.Directory.FullName } |
Select-Object -Unique
Additional information about MAX_PATH:
How do I find files with a path length greater than 260 characters in Windows?
Why does the 260 character path length limit exist in Windows?
http://www.powershellmagazine.com/2012/07/24/jaap-brassers-favorite-powershell-tips-and-tricks/
https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
https://gallery.technet.microsoft.com/scriptcenter/Get-ChildItemV2-to-list-29291aae
you cannot use get-childitem to list paths greater than the windows character limit.
There are a couple of alternatives for you. Try an external library like 'Alphafs' or you can use robocopy. Boe Prox has a script that utilizes robocopy and it is available on technet but i am not sure if it will work on PSV2. Anyway you can give it a try.
I've had a similar problem and resolved it like this:
$PathTooLong = #()
Get-ChildItem -LiteralPath $Path -Recurse -ErrorVariable +e -ErrorAction SilentlyContinue
$e | where {$_.Exception -like 'System.IO.PathTooLongException*'} | ForEach-Object {
$PathTooLong += $_.TargetObject
$Global:Error.Remove($_)
}
$PathTooLong
On every path that is too long, or that the PowerShell engine can't handle, Get-ChildItem will throw an error. This error is saved in the ErrorVariable called e in the example above.
When all errors are collected in $e you can filter out the ones you need by checking the error Exception for the string System.IO.PathTooLongException.
Hope it helps you out.

Format-List: sort properties by name

Is it possible to sort the output of the Format-List cmdlet by property name?
Suppose that I have an object $x with two properties "A" and "B", and when I run Format-List with it I get
(PS) > $x | Format-List
B : value b
A : value a
I would like to have
(PS) > $x | Format-List
A : value a
B : value b
NOTE: I should have specified from the beginning that, unlike in the example with "A" and "B" properties, the real object I have to deal with has quite a lot of properties, and new ones could be added in the future, so I don't know all the property names in advance.
AFAIK, Format-List does not provide such an option.
For your particular example this should work:
$x | Select-Object A, B | Format-List
If the property set is not fixed/known then the procedure will be more tricky with use of Get-Member and some preprocessing making sorted parameter array for Select-Object.
EDIT:
Here it is (let's use $host instead of $x):
$host | Select-Object ([string[]]($host | Get-Member -MemberType Property | %{ $_.Name } | Sort-Object)) | Format-List
Christopher is right, Select-Object is not absolutely needed:
$host | Format-List ([string[]]($host | Get-Member -MemberType Property | %{ $_.Name } | Sort-Object))
Nothing wrong with the accepted answer, but a really quick-and-dirty option for a one-off—that doesn't require having the collection already in a variable—might be...
... | Format-List | Out-String -Stream | Sort-Object
...which does a sort on each line of the output of Format-List.
Note that any property values that go onto the next line will be broken (and probably appear at the top of the output), but this could be fixed by the slightly-less-memorable...
... | Format-List | Out-String -Stream -Width ([Int32]::MaxValue) | Sort-Object
...at the expense of column indentation.
Of course, all object/pipeline info is lost by that Out-String call, although—considering the same is true of Format-List—you probably aren't going to care by that point.
Expanding on Christopher's idea, using get-member and format-list -Property:
$x | fl -property ($x| gm | sort name).name
The closest I can think of is to create a new psobject based off the old one but with the properties sorted e.g.:
$x | %{$obj = new-object psobject; `
$_.psobject.properties | Sort Name | `
%{Add-Member -Inp $obj NoteProperty $_.Name $_.Value}; $obj} | fl
You could get fancier and give the new psobject a typename that matches the old one, etc.
If you are dealing with a small number of properties, you can specify their order with the -Property parameter.
Here is an example:
Format-List -Property Owner, Path
If you have a lot of properties, I am not sure there is any easy way to sort them in Format-List, like Roman said.
This seems to work OK (edited so it accepts pipeline input):
function Format-SortedList
{
param (
[Parameter(ValueFromPipeline = $true)]
[Object]$InputObject,
[Parameter(Mandatory = $false)]
[Switch]$Descending
)
process
{
$properties = $InputObject | Get-Member -MemberType Properties
if ($Descending) {
$properties = $properties | Sort-Object -Property Name -Descending
}
$longestName = 0
$longestValue = 0
$properties | ForEach-Object {
if ($_.Name.Length -gt $longestName) {
$longestName = $_.Name.Length
}
if ($InputObject."$($_.Name)".ToString().Length -gt $longestValue) {
$longestValue = $InputObject."$($_.Name)".ToString().Length * -1
}
}
Write-Host ([Environment]::NewLine)
$properties | ForEach-Object {
Write-Host ("{0,$longestName} : {1,$longestValue}" -f $_.Name, $InputObject."$($_.Name)".ToString())
}
}
}
$Host, $MyInvocation | Format-SortedList
$Host, $MyInvocation | Format-SortedList -Descending
I feel sure that you can achieve the desired output. I suggest that you experiment with both Sort-Object (or plain Sort) and also Group-Object (plain Group)
My idea is to place the sort, or group before | format-list
Thus $x | sort-object -property xyz | Format-List
By using Select-Object with a calculated property (#{}) and then excluding it (-ExcludeProperty) you can also order the properties as you want. This works even when you don't know what's coming upfront.
#(
[PSCustomObject]#{
Color = 'Green'
Type = 'Fruit'
Name = 'kiwi'
Flavour = 'Sweet'
}
) | Select-Object #{Name = 'Flavour'; Expression = { $_.Flavour } },
#{Name = 'Name'; Expression = { $_.Name } }, * -ExcludeProperty Name, Flavour |
Format-List
Output:
Flavour : Sweet
Name : kiwi
Color : Green
Type : Fruit

PowerShell Script to Get a Directory Total Size

I need to get the size of a directory, recursively. I have to do this every month so I want to make a PowerShell script to do it.
How can I do it?
Try the following
function Get-DirectorySize() {
param ([string]$root = $(resolve-path .))
gci -re $root |
?{ -not $_.PSIsContainer } |
measure-object -sum -property Length
}
This actually produces a bit of a summary object which will include the Count of items. You can just grab the Sum property though and that will be the sum of the lengths
$sum = (Get-DirectorySize "Some\File\Path").Sum
EDIT Why does this work?
Let's break it down by components of the pipeline. The gci -re $root command will get all items from the starting $root directory recursively and then push them into the pipeline. So every single file and directory under the $root will pass through the second expression ?{ -not $_.PSIsContainer }. Each file / directory when passed to this expression can be accessed through the variable $_. The preceding ? indicates this is a filter expression meaning keep only values in the pipeline which meet this condition. The PSIsContainer method will return true for directories. So in effect the filter expression is only keeping files values. The final cmdlet measure-object will sum the value of the property Length on all values remaining in the pipeline. So it's essentially calling Fileinfo.Length for all files under the current directory (recursively) and summing the values.
If you are interested in including the size of hidden and system files then you should use the -force parameter with Get-ChildItem.
Here's quick way to get size of specific file extensions:
(gci d:\folder1 -r -force -include *.txt,*.csv | measure -sum -property Length).Sum
Thanks to those who posted here. I adopted the knowledge to create this:
# Loops through each directory recursively in the current directory and lists its size.
# Children nodes of parents are tabbed
function getSizeOfFolders($Parent, $TabIndex) {
$Folders = (Get-ChildItem $Parent); # Get the nodes in the current directory
ForEach($Folder in $Folders) # For each of the nodes found above
{
# If the node is a directory
if ($folder.getType().name -eq "DirectoryInfo")
{
# Gets the size of the folder
$FolderSize = Get-ChildItem "$Parent\$Folder" -Recurse | Measure-Object -property length -sum -ErrorAction SilentlyContinue;
# The amount of tabbing at the start of a string
$Tab = " " * $TabIndex;
# String to write to stdout
$Tab + " " + $Folder.Name + " " + ("{0:N2}" -f ($FolderSize.Sum / 1mb));
# Check that this node doesn't have children (Call this function recursively)
getSizeOfFolders $Folder.FullName ($TabIndex + 1);
}
}
}
# First call of the function (starts in the current directory)
getSizeOfFolders "." 0
To refine this answer by #JaredPar to be expanded and more performant:
function Get-DirectorySize() {
param ([string]$root = $(Resolve-Path .))
Get-ChildItem $root -Recurse -File |
Measure-Object -Property Length -Sum |
Select-Object -ExpandProperty Sum
}
Or, to make it more convenient for use explore type data:
Update-TypeData -TypeName System.IO.DirectoryInfo -MemberType ScriptProperty -MemberName Size -Value {
Get-ChildItem $this -Recurse -File |
Measure-Object -Property Length -Sum |
Select-Object -ExpandProperty Sum
}
Then use by Get-ChildItem | Select-Object Name,Length,Size

Resources