Select-Object output directly to a variable - powershell

I'm putting together a script to go to a bunch of domain computers and copy a file.
My code is:
Get-ChildItem -Path \\$computer -Filter $filename -Recurse -ErrorAction SilentlyContinue | Select-Object Directory -outvariable $directory
Now my problem is the result that is stored in the variable is #{Directory=\\Computer\dir
How do i make it output to the variable only the \\Computer\dir
Any help or guidance would be appreciated

In essence, your problem is a duplicate of How do I write the value of a single property of a object? (among others) - in short: use -ExpandProperty <propName> instead of just [-Property] <propName> in order to extract just the property value, rather than creating a custom object with a property of that name.
Additionally, your problem is that you must pass a mere variable name - without the $ sigil - to -OutVariable:
Select-Object -ExpandProperty Directory -OutVariable directory
That is, pass just directory to -OutVariable to have it fill variable $directory.
By contrast, -OutVariable $directory would fill a variable whose name is contained in variable $directory.

Select-Object by default creates an object that has the properties you selected. So in your case you get an object with a single property called Directory. If you are only selecting a single property you can use the ExpandProperty parameter to "promote", for lack of a better word, a property to an object.
Get-ChildItem -Path \\$computer -Filter $filename -Recurse -ErrorAction SilentlyContinue `
| Select-Object -ExpandProperty Directory -outvariable $directory

Related

Foreach/copy-item based on name contains

I'm trying to create a list of file name criteria (MS Hotfixes) then find each file name containing that criteria in a directory and copy it to another directory. I think I'm close here but missing something simple.
Here is my current attempt:
#Create a list of the current Hotfixes.
Get-HotFix | Select-Object HotFixID | Out-File "C:\Scripts\CurrentHotfixList.txt"
#
#Read the list into an Array (dropping the first 3 lines).
$HotfixList = Get-Content "C:\Scripts\CurrentHotfixList.txt" | Select-Object -Skip 3
#
#Use the Hotfix names and copy the individual hotfixes to a folder
ForEach ($Hotfix in $HotfixList) {
Copy-Item -Path "C:\KBtest\*" -Include *$hotfix* -Destination "C:\KBtarget"
}
If I do a Write-Host $Hotfix and comment out my Copy-Item line I get the list of hotfixes as expected.
If I run just the copy command and input the file name I am looking for - it works.
Copy-Item -Path "C:\KBtest\*" -Include *kb5016693* -Destination "C:\KBtarget"
But when I run my script it copies all the files in the folder and not just the one file I am looking for. I have several hotfixes in that KBtest folder but only one that is correct for testing.
What am I messing up here?
The simplest solution to your problem, taking advantage of the fact that -Include can accept an array of patterns:
# Construct an array of include patterns by enclosing each hotfix ID
# in *...*
$includePatterns = (Get-HotFix).HotfixID.ForEach({ "*$_*" })
# Pass all patterns to a single Copy-Item call.
Copy-Item -Path C:\KBtest\* -Include $includePatterns -Destination C:\KBtarget
As for what you tried:
To save just the hotfix IDs to a plain-text file, each on its own line, use the following, don't use Select-Object -Property HotfixId (-Property is implied if you omit it), use Select-Object -ExpandProperty HotfixId:
Get-HotFix | Select-Object -ExpandProperty HotFixID | Out-File "C:\Scripts\CurrentHotfixList.txt"
Or, more simply, using member-access enumeration:
(Get-HotFix).HotFixID > C:\Scripts\CurrentHotfixList.txt
Using Select-Object -ExpandProperty HotfixID or (...).HotfixID returns only the values of the .HotfixID properties, whereas Select-Object -Property HotfixId - despite only asking for one property - returns an object that has a .HotfixID property - this is a common pitfall; see this answer for more information.
Then you can use a Get-Content call alone to read the IDs (as strings) back into an array (no need for Select-Object -Skip 3):
$HotfixList = Get-Content "C:\Scripts\CurrentHotfixList.txt"
(Note that, as the solution at the top demonstrates, for use in the same script you don't need to save the IDs to a file in order to capture them.)
This will likely fix your primary problem, which stems from how Out-File creates for-display string representations of the objects ([pscustomobject] instances) that Select-Object -Property HotfixID created:
Not only is there an empty line followed by a table header at the start of the output (which your Select-Object -Skip 3 call skips), there are also two empty lines at the end.
When these empty lines were read into $hotfix in your foreach loop, -Include *$hotfix* effectively became -Include **, which therefore copied all files.
first, you do not need to create and import those textfiles:
get-hotfix | ?{$_.hotfixid -notmatch 'KB5016594|KB5004567|KB5012170'} | %{
copy-item -path "C:\kbtest\$($_.HotfixId).exe" -Destination "C:\kbTarget"
}
This filters for the hotfixes you do not want, if you do not need it remove:
?{$_.hotfixid -notmatch 'KB5016594|KB5004567|KB5012170'}
I assume that those files are exe files in my example.

PowerShell script that accepts pipeline input?

Need a little help to tweak this script that copies the latest file to another folder:
$FilePath = "C:\Downloads\Sales 202112*.xlsx"
$DestinationPath = "C:\myFiles\"
gci -Path $FilePath -File |
Sort-Object -Property LastWriteTime -Descending |
Select FullName -First 1 |
Copy-Item $_ -Destination $DestinationPath
Not sure how to reference pipeline input for the Copy-Item command.
Thanks.
tl;dr
Get-ChildItem -Path $FilePath -File |
Sort-Object -Property LastWriteTime -Descending |
Select-Object -First 1 | # Note: No 'FullName'
Copy-Item -Destination $DestinationPath # Note: No '$_'
The simplest and most robust approach is to pipe Get-ChildItem / Get-Item output as-is to other file-processing cmdlets, which binds to the latter's -LiteralPath parameter, i.e the input file path.
As for what you tried:
The automatic $_ variable, which explicitly refers to the pipeline input object at hand, is only needed (and supported) inside script blocks ({ ... }) passed to cmdlets.
With suitable pipeline input, PowerShell implicitly binds it to a pipeline-binding parameter of the target cmdlet, which in case of the Copy-Item call above is -LiteralPath. In other words: specifying a value for the target parameter as an argument isn't necessary.
This answer explains the mechanism that binds the System.IO.FileInfo and System.IO.DirectoryInfo instances that Get-ChildItem outputs to the -LiteralPath parameter of file-processing cmdlets such as Copy-Item.
Note that, with FullName out of the picture (see below), it is indeed a System.IO.FileInfo instance that Copy-Item receives as pipeline input, because the Sort-Object ... and Select-Object -First 1 calls above pass them through without changing their type.
Selecting the FullName property in an attempt to pass only the file's full path (as a string) via the pipeline is unnecessary, and, more importantly:
It fails, unless you use -ExpandProperty FullName to ensure that only the property value is output; without it, you get an object that has a .FullName property - see this answer for more information.
Even if you fix that, the solution is - at least hypothetically - less robust than passing the System.IO.FileInfo instances as a whole: mere string input binds to the -Path rather than the -LiteralPath parameter, which means that what is by definition a literal path is interpreted as a wildcard expression, which notably causes mishandling of literal paths that contain [ characters (e.g. c:\foo\file[1].txt).
See this answer for how to inspect the specifics of the pipeline-binding parameters of any given cmdlet.

Exclude extension from an output file in PS?

My goal is to display items in the directory C:\test in a log file called log.txt without displaying the file-extensions of the files found, e.g. .zip, .pdf, etc.
My script so far:
Get-ChildItem -Path C:\Test\ -name |Out-File C:\test2\log.txt
How do I get the .log file to NOT display the extensions of the files found in the C:\test folder?
Use BaseName property instead of Name:
Get-ChildItem -Path C:\Test\ | Select-Object -ExpandProperty BaseName | Out-File C:\test2\log.txt
As there's no built-in -BaseName property for Get-ChildItem cmdlet, you need to get that property using Select-Object. Expanding the property allows you to get only the value of chosen property, without the header.
Another way to get BaseName value would be to use .BaseName like this:
(Get-ChildItem -Path C:\Test\).BaseName | Out-File C:\test2\log.txt
That form is shorter, but personally I prefer the first one due to readability and no need to remember about surrounding braces ().
Best practice
If you want to inspect what are the possible properties (and their values) of the object you have, you can also use Select-Object for that:
# Warning: HUGE OUTPUT POSSIBLE
Get-ChildItem -Path C:\test\| Select-Object *
# It's usually good to take only one object from the array
$obj = (Get-ChildItem -Path C:\test\)[0]
$obj | Select-Object *

Powershell ForEach Loop Failing

I'm trying to generate a report for all WIM files in my MDT Deployment Share. Basically, I think need to do a ForEach loop on all the WIM files found. I have what I think should work but, obviously, it doesn't. How far off am I?
$WimPath = "G:\DeploymentShare\Operating Systems"
Get-ChildItem -Path $WimPath -Filter *.wim -Recurse -File | Select-Object -ExpandProperty VersionInfo | Select-Object FileName | ForEach-Object { Get-WindowsImage -ImagePath $_ }
The error I'm seeing is nagging about the Parameter being incorrect for the Get-WindowsImage command.
Get-WindowsImage : The parameter is incorrect.
At line:3 char:147
+ ... t-Object FileName | ForEach-Object { Get-WindowsImage -ImagePath $_ }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I'm thinking my Select-Object isn't working like I think it should or I'm not using the pipeline correctly in my Get-WindowsImage command.
I'm a PowerShell noob and don't fully understand this, but I think what is going on can be explained by first focusing on this part of the command:
Get-ChildItem -Path $WimPath -Filter *.wim -Recurse -File | Select-Object -ExpandProperty VersionInfo | Select-Object FileName
To get this far, we first get all the *.wim files in your path, we expand the VersionInfo property, and then select the FileName. In the console, that will show you results like this:
FileName
--------
[files here]
The trick is in understanding what PowerShell is telling you with this output. The fact you see a FileName header means the pipeline has a stream of Objects with one property named FileName. Then we send that stream of Objects to ForEach-Object and look at the $_ special variable.
Hopefully it is clearer now what is going on. Get-WindowsImage -ImagePath $_ wants to see a string value holding the path of a *.wim file. But we sent it an object with one property.
You can fix this a few ways... adding ExpandProperty to the second Select-Object would probably do it. But really there's no reason for two Select-Objects in there at all. I think you could just do this:
Get-ChildItem -Path $WimPath -Filter *.wim -Recurse -File | Select-Object -ExpandProperty FullName | ForEach-Object { Get-WindowsImage -ImagePath $_ }
And the trick here is the string representation you see in the shell from Get-ChildItem doesn't necessarily show every property in the object. There wouldn't be space. The FileName was always there, and you can see it by checking the Get-Member cmdlet, like so:
Get-ChildItem -Path $WimPath -Filter *.wim -Recurse -File | Get-Member

List file names in a folder matching a pattern, excluding file content

I am using the below to recursively list all files in a folder that contains the $pattern
Get-ChildItem $targetDir -recurse | Select-String -pattern "$pattern" | group path | select name
But it seems it both list files having the $pattern in its name and in its content, e.g. when I run the above where $pattern="SAMPLE" I get:
C:\tmp\config.include
C:\tmp\README.md
C:\tmp\specs\SAMPLE.data.nuspec
C:\tmp\specs\SAMPLE.Connection.nuspec
Now:
C:\tmp\config.include
C:\tmp\README.md
indeed contains the SAMPLE keywords/text but I don't care about that, I only need the command to list file names not file with content matching the pattern. What am I missing?
Based on the below answers I have also tried:
$targetDir="C:\tmp\"
Get-ChildItem $targetDir -recurse | where {$_.name -like "SAMPLE"} | group path | select name
and:
$targetDir="C:\tmp\"
Get-ChildItem $targetDir -recurse | where {$_.name -like "SAMPLE"} | select name
but it does not return any results.
Select-String is doing what you told it to. Emphasis mine.
The Select-String cmdlet searches for text and text patterns in input strings and files.
So if you are just looking to match with file names just use -Filter of Get-ChildItem or post process with Where-Object
Get-ChildItem -Path $path -Recurse -Filter "*sample*"
That should return all files and folders that have sample in their name. If you just wanted files or directories you would be able to use the switches -File or -Directory to return those specific object types.
If your pattern is more complicated than a simple word then you might need to use Where-Object like in Itchydon's answer with something like -match giving you access to regex.
The grouping logic in your code should be redundant since you are returning single files that all have unique paths. Therefore I have not included that here. If you just want the paths then you can pipe into Select-Object -Expand FullName or just (Get-ChildItem -Path $path -Recurse -Filter "*sample*").Fullname
get-ChildItem $targetDir -recurse | where {$_.name -like $pattern} | select name
To complement Matt's helpful answer:
Specifically, because what you're piping to Select-String are [System.IO.FileInfo] objects - which is what Get-ChildItem outputs - rather than strings, it is the contents of the files represented by these objects is being searched.
Assuming that you need to match only the file name part of each file's path and that your pattern can be expressed as a wildcard expression, you do not need Select-String at all and can instead use Get-ChildItem with -Filter, as in Matt's answer, or the slower, but slightly more powerful -Include.
Caveat:
Select-String -Pattern accepts a regular expression (e.g., .*sample.*; see Get-Help about_Regular_Expressions),
whereas Get-ChildItem -Filter/-Include accepts a wildcard expression (e.g., *sample*; see Get-Help about_Wildcards) - they are different things.
On a side note: If your intent is to match files only, you can tell Get-ChildItem to restrict output to files (as opposed to potentially also directories) using -File (analogously, you can limit output to directories with -Directory).
Group-Object path (group path) will not work as intended, because the .Path property of the match-information objects output by Select-String contains the full filename, so you'd be putting each file in its own group - essentially, a no-op.
When using just Get-ChildItem, the equivalent property name would be .FullName, but what you're looking for is to group by parent path (the containing directory's path), .DirectoryName), I presume, therefore:
... | Group-Object DirectoryName | Select-Object Name
This outputs the full path of each directory that contains at least 1 file with a matching file name.
(Note that the Name in Select-Object Name refers to the .Name property of the group objects returned by Group-Object, which in this case is the value of the .DirectoryName property on the input objects.)
To complement the excellent answer by #mklement0, you can ask Powershell to print the full path by appending a pipe as follows:
Get-ChildItem -Recurse -ErrorAction SilentlyContinue -Force -Filter "*sample*" | %{$_.FullName}
Note: When searching folders where you might get an error based on security, hence we use the SilentlyContinue option.
I went through the answer by #Itchydon
but couldn't follow the use of '-like' $pattern.
I was trying to list files having 32characters(letters and numbers) in the filename.
PS C:> Get-ChildItem C:\Users\ -Recurse | where {$_.name -match "[a-zA-Z0-9]{32}"} | select name
or
PS C:> Get-ChildItem C:\Users\010M\Documents\WindowsPowerShell -Recurse | Where-Object {$_.name -match "[A-Z0-9]{32}"} | select name
So, in this case it doesn't matter whether you use where or where-object.
You can use select-string directly to search for files matching a certain string, yes, this will return the filename:count:content ... etc, but, internally these have names that you can chose or omit, the one you need is the "filename" to do this pipe this into "select-object" choosing the "FileName" from the output.
So, to select all *.MSG files that has the pattern of "Subject: Webservices restarted", you can do the following:
Select-String -Path .*.MSG -Pattern 'Subject: WebServices Restarted'
-List | select-object Filename
Also, to remove these files on the fly, you could pip into a ForEach statement with the RM command as follows:
Select-String -Path .*.MSG -Pattern 'Subject: WebServices Restarted'
-List | select-object Filename | foreach { rm $_.FileName }