I have the power shell below that selectes certain fields
dir -Path E:\scripts\br\test | Get-FileMetaData | Select-Object name, Comments, Path, Rating
what i want to do is utilize Name,Comments,Path,Rating in further Pipes $_.name etc dosnt work
If I understand your question correctly, you want to do something with the output of Select-Object, but you want to do it in a pipeline.
To do this, you need to pass the output down the pipeline into a Cmdlet that accepts pipeline input (such as ForEach-Object). If the next operation in the pipeline does not accept pipeline input, you will have to set the output to a variable and access the information through the variable,
Using ForEach-Object
In this method, you will be processing each object individually. This will be similar to the first option in Method 1 (that is, dealing with individual items in the collection of items returned by Select-Object).
dir | Get-FileMetaData | Select-Object Name,Comments,Path,Rating | ForEach-Object {
# Do stuff with $_
# Note that $_ is a single item in the collection returned by Select-Object
}
The variable method is included in case your next Cmdlet does not accept pipeline input.
Using Variable
In this method, you will treat $tempVariable as an array and you can operate on each item. If need be, you can actually access each column individually, getting everything at once.
$tempVariable = dir | Get-FileMetaData | Select-Object Name,Comments,Path,Rating
# Do stuff with each Name by using $tempVariable[i].Name, etc.
# Or do stuff with all Names by using $tempVariable.Name, etc.
Related
I am trying to find a method to map IDs of multiple users to their associated email addresses in Active Directory (AD), and subsequently append the outputs into a txt file, ultimately generating a single file with a list of email addresses. Via the following command leveraging PowerShell AD Tools, I can output the email address of a certain user:
$user= testID
Get-ADUser $user -server ml -Properties * | Select-Object mail
Now I'm trying to adapt this to work across multiple users, although the method I've come across does not append or concatenate each result to the txt file. Each new output when the loop iterates overwrites the contents of the existing text file.
$multiple_users = "testID1", "testID2", "testID3"
foreach ($multiple_user in $multiple_users){
Get-ADUser $multiple_user -server ml -Properties * | Select-Object mail > ID_to_email.txt
}
Any direction or insight, is much appreciated!
Thank you
Building on Abraham Zinala helpful comment:
The immediate fix is to replace > with >> inside your foreach loop:
Redirection > is in effect an alias for Out-File therefore replaces the target file in every iteration, which is not what you want.
By contrast, >> is in effect an alias for Out-File -Append and therefore appends to the target file.
However, using >> inside a loop is best avoided, because it is:
inefficient, because the target file must be opened and closed in every iteration.
inconvenient, in that you must ensure that the target file doesn't exist yet or is empty before the loop, so as not to accidentally append to preexisting content.
Therefore, it is preferable to use a pipeline with a single Out-File call / > operation that receives the output from all iterations in a streaming fashion:
Note:
A foreach statement cannot directly be used in a pipeline and therefore with a redirection, which is why the similar ForEach-Object cmdlet is used below, to which the $multiple_users array is piped, causing its elements to be processed one by one, as reflected in the automatic $_ variable.
Alternatively, you can wrap a statement such as foreach or while in & { ... } or . { ... } to allow it to participate in a pipeline; e.g.:& { foreach ($i in 1..2) { $i } } > out.txt
The solution below applies analogously to use of the Set-Content cmdlet (which is more efficient than > / Out-File if you know the objects to write to the file to be strings already): use a single Set-Content call at the end of the pipeline (instead of calling Add-Content in every loop iteration).
$multiple_users |
ForEach-Object {
Get-ADUser $_ -server ml -Properties mail |
Select-Object mail
} > ID_to_email.txt # Write ALL output to ID_to_email.txt
Instead of > ID_to_email.txt, you could use | Out-File ID_to_email.txt instead; explicit use of Out-File is required in the following cases:
to force interpretation of the target file path as a literal path, with -LiteralPath, notably if it contains [ and ] (perhaps surprisingly, > and Out-File with the (often positionally implied) -FilePath parameter treat paths as wildcard expressions - see this answer).
to control the character-encoding to use for the output file, via the -Encoding parameter.
I have this working, but need LastWriteTime and can't get it.
Get-ChildItem -Recurse | Select-String -Pattern "CYCLE" | Select-Object Path, Line, LastWriteTime
I get an empty column and zero Date-Time data
Select-String's output objects, which are of type Microsoft.PowerShell.Commands.MatchInfo, only contain the input file path (string), no other metadata such as LastWriteTime.
To obtain it, use a calculated property, combined with the common -PipelineVariable parameter,
which allows you to reference the input file at hand in the calculated property's expression script block as a System.IO.FileInfo instance as output by Get-ChildItem, whose .LastWriteTime property value you can return:
Get-ChildItem -File -Recurse -PipelineVariable file |
Select-String -Pattern "CYCLE" |
Select-Object Path,
Line,
#{
Name='LastWriteTime';
Expression={ $file.LastWriteTime }
}
Note how the pipeline variable, $file, must be passed without the leading $ (i.e. as file) as the -PipelineVariable argument . -PipelineVariable can be abbreviated to -pv.
LastWriteTime is a property of System.IO.FileSystemInfo, which is the base type of the items Get-ChildItem returns for the Filesystem provider (which is System.IO.FileInfo for files). Path and Line are properties of Microsoft.PowerShell.Commands.MatchInfo, which contains information about the match, not the file you passed in. Select-Object operates on the information piped into it, which comes from the previous expression in the pipeline, your Select-String in this case.
You can't do this as a (well-written) one-liner if you want the file name, line match, and the last write time of the actual file to be returned. I recommend using an intermediary PSCustomObject for this and we can loop over the found files and matches individually:
# Use -File to only get file objects
$foundMatchesInFiles = Get-ChildItem -Recurse -File | ForEach-Object {
# Assign $PSItem/$_ to $file since we will need it in the second loop
$file = $_
# Run Select-String on each found file
$file | Select-String -Pattern CYCLE | ForEach-Object {
[PSCustomObject]#{
Path = $_.Path
Line = $_.Line
FileLastWriteTime = $file.LastWriteTime
}
}
}
Note: I used a slightly altered name of FileLastWriteTime to exemplify that this comes from the returned file and not the match provided by Select-String, but you could use LastWriteTime if you wish to retain the original property name.
Now $foundMatchesInFiles will be a collection of files which have CYCLE occurring within them, the path of the file itself (as returned by Select-String), and the last write time of the file itself as was returned by the initial Get-ChildItem.
Additional considerations
You could also use Select-Object and computed properties but IMO the above is a more concise approach when merging properties from unrelated objects together. While not a poor approach, Select-Object outputs data with a type containing the original object type name (e.g. Selected.Microsoft.PowerShell.Commands.MatchInfo). The code may work fine but can cause some confusion when others who may consume this object in the future inspect the output members. LastWriteTime, for example, belongs to FileSystemInfo, not MatchInfo. Another developer may not understand where the property came from at first if it has the MatchInfo type referenced. It is generally a better design to create a new object with the merged properties.
That said this is a minor issue which largely comes down to stylistic preference and whether this object might be consumed by others aside from you. I write modules and scripts that many other teams in my organization consume so this is a concern for me. It may not be for you. #mklement0's answer is an excellent example of how to use computed properties with Select-Object to achieve the same functional result as this answer.
I want to chain cmdlets together in a pipeline starting with a Import-Csv file of NN records to process, at the end I want to write out a result file for all NN records and the accumulated results of processing. Along the way I may want to add data to the pipeline that says don't process this record any further, but still pass it along in the pipeline.
I envisioned this looking like this:
Import-Csv input | step-1 -env DEV | step-2 | step-3 | Export-Csv result
Each cmdlet being written to pipe all $_ properties for each record and keep them in the pipeline.
What's the best way to read some sort of "CanContinue" property and if it is false short circuit processing and just pass it along to the next cmdlet in the pipeline without processing?
I'm assuming you don't want this flag to be part of the resulting CSV. The way I see it you can use 2 similar approaches: add the flag to the object being processed before returning it, or wrap the object in another object (which contains the flag and another property that holds the original object).
For now I'm going to explore the first option where you add the property. I'm going to change it from a positive CanContinue to a negative, DoNotProcess so that its non-existence can coalesce to $false (do continue).
To do this, in each of your processing functions, just check the value of the DoNotContinue property. If it's $true, return the original object you received without additional processing.
If it's $false, you can do your processing and if the conditions are met that processing should stop, you force add the property with $true:
Process {
# processing done
$MyObj |
Add-Member -NotePropertyName DoNotContinue -NotePropertyValue $true -Force -PassThru
}
All such commands can handle it this way.
Now when it comes to the end of the pipeline, you don't want this property written to the CSV. For that, strip it off with Select-Object:
Import-Csv input |
step-1 -env DEV |
step-2 |
step-3 |
Select-Object -Property * -ExcludeProperty DoNotContinue |
Export-Csv result
Bonus:
Refer back to my answer on another question of yours, and instead of manually checking for the property, define it as a parameter in your processing cmdlets with [Parameter(ValueFromPipelineByPropertyName)] like so:
param(
[Parameter(ValueFromPipelineByPropertyName)]
[Switch]
$DoNotContinue
)
Why do that? Because you let PowerShell process the object for you and you only have to check the value of $DoNotContinue. It also allows you to override that value for a particular call.
(in this case, I'd rename it to $DoNotProcess or $SkipProcessing or something; remember you can also use [Alias()] if you want it to have multiple names)
I'm developing a cmdlet called Merge-Xsd that can merge similar XML schemas. It takes a list of paths, loads the schemas, merges them, and produces an XMLDocument as output.
All schemas of a particular file name are considered "similar", and so what I'm doing is getting all of the child items in a particular directory structure, grouping them according to the file name, and then trying to pass them to my custom cmdlet.
Grouping them is easy:
$grouping = Get-ChildItem -Recurse -Filter *.xsd |
Group-Object -Property Name -AsHashTable -AsString
However, processing them as part of the same pipeline is not. I've gotten as close as this:
$grouping.Keys |
ForEach-Object { ($grouping[$_] |
Select-Object -ExpandProperty FullName | Merge-Xsd).Save("C:\Out\$_") }
But what I'd really like to be able to do is use ForEach-Object directly after Group-Object to iterate over each group item, thus eliminating the need for the separate $grouping variable.
How can I use ForEach-Object to get the key/value pair while keeping each invocation of Merge-Xsd scoped to that particular key/value pair?
20150224 UPDATE:
The Merge-Xsd option set is extremely basic:
NAME
Merge-Xsd
SYNTAX
Merge-Xsd [-Path] <string[]> [<CommonParameters>]
It is really just intended for throwing a bunch of files at it in one go and having them merged into a single output, which is an XmlDocument. (I modeled the output off of ConvertTo-Xml.)
I think you could just nest it like this:
Get-ChildItem -Recurse -Filter *.xsd |
Group-Object -Property Name |
ForEach-Object {
($_.Group.FullName | Merge-Xsd).Save("C:\Out\$($_.Name)")
}
I don't have your cmdlet or files but in my limited testing this would work.
Some Explanation
I took out the -AsHash and -AsString parameters so we could deal directly with the group objects returned by Group-Object.
The $_.Group.FullName is more complex than it seems on first glance. $_ here refers to a single group object, since we're in a ForEach-Object. The group object contains a property called Name which is the name of the group, and a property called Group which is actually a collection of the the individual items within the group, so $_.Group is a collection.
From here, it would make sense to pipe that to ForEach-Object again, since each of the items in that collection will be a FileInfo object, and you want to get the FullName property to pass to Merge-Xsd.
Here we take advantage of some powershell magic. When you refer to $c.Property where $c is a collection of objects with a Property property, you get back a collection that consists of the property objects.
So $props = $c.Property is the same as:
$props = $c | ForEach-Object { $_.Property }
Knowing that, we can pipe $_.Group.FullName directly into Merge-Xsd to pass along all of the fullnames from all of the files in the group.
In that context, $_.Name still refers to the group object, so it's the name of the group, not the name of the file.
The following code gives me an array of PSCustomObjects, how can I get it to return an array of Strings?
$files = Get-ChildItem $directory -Recurse | Select-Object FullName | Where-Object {!($_.psiscontainer)}
(As a secondary question, what's the psiscontainer part for? I copied that from an example online)
Post-Accept Edit: Two great answers, wish I could mark both of them. Have awarded the original answer.
You just need to pick out the property you want from the objects. FullName in this case.
$files = Get-ChildItem $directory -Recurse | Select-Object FullName | Where-Object {!($_.psiscontainer)} | foreach {$_.FullName}
Edit: Explanation for Mark, who asks, "What does the foreach do? What is that enumerating over?"
Sung Meister's explanation is very good, but I'll add a walkthrough here because it could be helpful.
The key concept is the pipeline. Picture a series of pingpong balls rolling down a narrow tube one after the other. These are the objects in the pipeline. Each stage of pipeline--the code segments separated by pipe (|) characters--has a pipe going into it and pipe going out of it. The output of one stage is connected to the input of the next stage. Each stage takes the objects as they arrive, does things to them, and sends them back out into the output pipeline or sends out new, replacement objects.
Get-ChildItem $directory -Recurse
Get-ChildItem walks through the filesystem creating FileSystemInfo objects that represent each file and directory it encounters, and puts them into the pipeline.
Select-Object FullName
Select-Object takes each FileSystemInfo object as it arrives, grabs the FullName property from it (which is a path in this case), puts that property into a brand new custom object it has created, and puts that custom object out into the pipeline.
Where-Object {!($_.psiscontainer)}
This is a filter. It takes each object, examines it, and sends it back out or discards it depending on some condition. Your code here has a bug, by the way. The custom objects that arrive here don't have a psiscontainer property. This stage doesn't actually do anything. Sung Meister's code is better.
foreach {$_.FullName}
Foreach, whose long name is ForEach-Object, grabs each object as it arrives, and here, grabs the FullName property, a string, from it. Now, here is the subtle part: Any value that isn't consumed, that is, isn't captured by a variable or suppressed in some way, is put into the output pipeline. As an experiment, try replacing that stage with this:
foreach {'hello'; $_.FullName; 1; 2; 3}
Actually try it out and examine the output. There are four values in that code block. None of them are consumed. Notice that they all appear in the output. Now try this:
foreach {'hello'; $_.FullName; $ x = 1; 2; 3}
Notice that one of the values is being captured by a variable. It doesn't appear in the output pipeline.
To get the string for the file name you can use
$files = Get-ChildItem $directory -Recurse | Where-Object {!($_.psiscontainer)} | Select-Object -ExpandProperty FullName
The -ExpandProperty parameter allows you to get back an object based on the type of the property specified.
Further testing shows that this did not work with V1, but that functionality is fixed as of the V2 CTP3.
For Question #1
I have removed "select-object" portion - it's redundant and moved "where" filter before "foreach" unlike dangph's answer - Filter as soon as possible so that you are dealing with only a subset of what you have to deal with in the next pipe line.
$files = Get-ChildItem $directory -Recurse | Where-Object {!$_.PsIsContainer} | foreach {$_.FullName}
That code snippet essentially reads
Get all files full path of all files recursively (Get-ChildItem $directory -Recurse)
Filter out directories (Where-Object {!$_.PsIsContainer})
Return full file name only (foreach {$_.FullName})
Save all file names into $files
Note that for foreach {$_.FullName}, in powershell, last statement in a script block ({...}) is returned, in this case $_.FullName of type string
If you really need to get a raw object, you don't need to do anything after getting rid of "select-object". If you were to use Select-Object but want to access raw object, use "PsBase", which is a totally different question(topic) - Refer to "What's up with PSBASE, PSEXTENDED, PSADAPTED, and PSOBJECT?" for more information on that subject
For Question #2
And also filtering by !$_.PsIsContainer means that you are excluding a container level objects - In your case, you are doing Get-ChildItem on a FileSystem provider(you can see PowerShell providers through Get-PsProvider), so the container is a DirectoryInfo(folder)
PsIsContainer means different things under different PowerShell providers;
e.g.) For Registry provider, PsIsContainer is of type Microsoft.Win32.RegistryKey
Try this:
>pushd HKLM:\SOFTWARE
>ls | gm
[UPDATE] to following question: What does the foreach do? What is that enumerating over?
To clarify, "foreach" is an alias for "Foreach-Object"
You can find out through,
get-help foreach
-- or --
get-alias foreach
Now in my answer, "foreach" is enumerating each object instance of type FileInfo returned from previous pipe (which has filtered directories). FileInfo has a property called FullName and that is what "foreach" is enumerating over.
And you reference object passed through pipeline through a special pipeline variable called "$_" which is of type FileInfo within the script block context of "foreach".
For V1, add the following filter to your profile:
filter Get-PropertyValue([string]$name) { $_.$name }
Then you can do this:
gci . -r | ?{!$_.psiscontainer} | Get-PropertyName fullname
BTW, if you are using the PowerShell Community Extensions you already have this.
Regarding the ability to use Select-Object -Expand in V2, it is a cute trick but not obvious and really isn't what Select-Object nor -Expand was meant for. -Expand is all about flattening like LINQ's SelectMany and Select-Object is about projection of multiple properties onto a custom object.