gsutil - cp not processing each item in the pipeline - powershell

I am trying to create a small Powershell script which will copy a list of files matching a specific condition to a specified GCP Storage Bucket. I have gotten this far:
Get-ChildItem $Path | Where-Object { $_.psiscontainer -and $_.LastWriteTime -gt $Age } | Select-Object -ExpandProperty FullName | ft -hidetableheaders | gsutil -m cp -L log.log -r -n -I gs://bucket
But this only uploads the contents of the first folder in the list. I've tried using a foreach-object on the gsutil command, but I get an error due to not finding a URL to upload. When writing the output of the foreach to the console, the output appears to be completely empty.
I have confirmed that the entire line minus the gsutil command returns the correct folders from the path, so I know that the data is going into the pipeline. But I'm not sure why gsutil is only considering the first item in the pipeline.
Any assistance would be greatly appreciated, and thank you in advance!

My "c:\temp" folder has two child folders. When I run Get-ChildItem "c:\temp" | Where-Object {$_.psiscontainer} | Select-Object -ExpandProperty FullName | ft -hidetableheaders Powershell does output the names of the two child folders in c:\temp to the console:
C:\temp\child folder A
C:\temp\child folder B
However, if you capture the output of the command and examine the data type of each path output, you'll see that they are not strings, and I think gsutil requires a string as input when using -I (I think Ansgar Wiechers's comment is correct)
Run this:
$x = Get-ChildItem "c:\temp"| Where-Object {$_.psiscontainer} | Select-Object -ExpandProperty FullName | ft -hidetableheaders
write-host $x.Count
$x[0] | get-member
In my case, I see a count of 2 as expected (two child folders)
However, the datatype of the first item is not a string, it is a FormatEntryData:
$x[0] | get-member
shows the following on the console:
TypeName: Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData
If you remove the | ft -hidetableheaders portion of your command, the data type of each item is a string
Run this:
$x = Get-ChildItem "c:\temp"| Where-Object {$_.psiscontainer} | Select-Object -ExpandProperty FullName
write-host $x.Count
$x[0] | get-member
You'll see a data type of TypeName: System.String for $x[0]
Does this work with gsutil?

As Ansgar Wiechers already said, do not use Format-* cmdlets unless you have a specific need to display formatted output to a user. If it still copies just the first directory the parameter -I may not work as it should. Try ... | ForEach-Object { gsutil -m cp -n -r $_ gs://... } instead.

Related

Return the second level directory names only

I would like to retrieve all (and only) second level directory names of my disk. For example, C:\folder1\folder2 and C:\folder1\folder3, I need to retrieve only folder2 and folder3.
I write this and the PS displays all the directory names:
Get-ChildItem -Recurse | ?{ $_.PSIsContainer} | Select-Object Name
I found this help, and I modify the previous command in this way:
Get-ChildItem -Recurse | `Where-Object {($_.directory -match '^*\\\S*$')} ` | ForEach-Object {?{ $_.PSIsContainer} | Select-Object Name }
but when I use it the PS doesn't display anything.
I can't understand why, someone can help me? Thank you!
Only files appear to have a .directory property, directories do not, so you will never get something which passes your (.directory matches a pattern) filter and also passes your (PSIsContainer) filter.
Except that your PSIsContainer filter doesn't work:
| ForEach-Object {?{ $_.PSIsContainer} | Select-Object Name }
this doesn't make sense; you can only filter the pipeline using ? with cmdlet | ? {}, you cannot filter at the start of a loop scriptblock with no input and get anything useful. This is running where-object {} over and over in a loop, - and that has no output.
Using -Recurse will be very slow, as you go into every single directory all the way to the end, and make [fileinfo] objects for all the files as well.
Apart from Matt's wildcard answer, assuming PS v3 or above, you could list all the directories in the root, and then all the directories inside those, and stop there:
Get-ChildItem c:\ -Directory | Get-ChildItem -Directory | Select -ExpandProperty Name
or
gci c:\ -Dir | ForEach { (gci $_ -Dir).Name }
You should just be able to use some fun wildcards to get what you want here.
Get-ChildItem \*\*\ | Where-Object{$_.PSIsContainer}
Or if you have at least PowerShell 3.0 this would be faster
Get-ChildItem \*\*\ -Directory
Then if you wanted just the names tack on | Select-Object -ExpandProperty Name
Here with full path, network compatible:
(Get-ChildItem "\\folder1\" -Dir).fullname | ForEach {(Get-ChildItem $_ -Dir).name}
Want it stored in an array?
$subfolders = (Get-ChildItem "\\folder1\" -Dir).fullname | ForEach {(Get-ChildItem $_ -Dir).name}

Files sorting with version number in Powershell

I have this folder in a directory. With different version on them.
CD1,CD2,CD3,CD4,CD5,CD6,CD7,CD8,CD9,CD11,CD12
I'm new to powershell, can anyone help me to get the latest version folder from the above folders? Here CD12 is the latest folder. I can't use last modified time because I copy them at the same time.
$FolderName=(Get-ChildItem C:\Current\CD |Where-Object {$_.name -like "*CD*"}| sort{$_.name.Substring(2,2)}|Select-Object Name -Last 1).Name)
Write-Host "$FolderName"
I tried the above script and it did not help. Can anyone help me?
The next new version is CD13, and the script should get that folder
You can try something like below
$max_version = Get-ChildItem "C:\Current\" | Where-Object {$_.PSIsContainer}
| Foreach-Object {$_.Name} | Foreach-object {$_ -replace "CD", ""}
| measure -maximum | Select-Object -expand Maximum
Write-host ("CD" + $max_version)
Which will result in CD12
You almost have it. When I tried to run your code, I ran into two errors. First, you have an extra ')' at the end of the line causing a syntax error. Second, your 'SubString()' call is failing because you're trying to get the 3rd and 4th characters of a string without a 4th character ("CD1"). You don't need the scriptblock to your Sort command, though. You can just sort on the Name field.
$FolderName = Get-ChildItem C:\7005\Hot-Fix\CD | where Name -like "CD*" | sort Name | Select-Object -Last 1 -ExpandProperty Name
As a side note, this uses the PowerShell 3 syntax for Where-Object and Sort-Object to omit the {}. And it uses the -ExpandProperty parameter to Select-Object, so you don't have to wrap the whole thing in parens to get the Name property.
You could try this:
#requires -v 3
$baseFolder='C:\7005\Hot-Fix\CD'
$folder=dir $baseFolder\CD* -Directory |
? basename -CMatch 'CD\d{1,}' |
sort #{e={'{0:0000}' -f [int]($_ -replace '\D')}} -Descending |
select -First 1
Notice, I'm considering case sensitive matching; also, $folder contains what you're looking for.

Powershell Where-Object -in clause

I am attempting to delete some orphaned folders. I have a list of folder names that are orphaned, and I am attempting to find the corresponding File object. Unfortuantely, I can't seem to get the -in clause to work as I would expect.
A slightly contrived example:
$orphans = ls | select Name
ls | ?{$_.Name -in $orphans}
ls -Include $orphans
No files are returned.
In order for this to work you need to do
$orphans = ls | select -ExpandProperty Name
Or inside the Where-Clause
ls | ?{$_.Name -in $orphans.name}
$orphans in your code is an object array with a name property. Break that property out into a simple string array and you should get the results you expect.
I will also assume that the code sample is just that... a sample. That code, once working correctly, seems redundant.
Powershell v2
The above code didn't work on Powershell v2. A combination of -ExpandProperty and -contains seemed to correct the issue:
$orphans = ls | select -ExpandProperty Name
ls | ?{$orphans -contains $_.Name}

Delete files containing string

How can I delete all files in a directory that contain a string using powershell?
I've tried something like
$list = get-childitem *.milk | select-string -pattern "fRating=2" | Format-Table Path
$list | foreach { rm $_.Path }
And that worked for some files but did not remove everything. I've tried other various things but nothing is working.
I can easily get the list of file names and can create an array with the path's only using
$lista = #(); foreach ($f in $list) { $lista += $f.Path; }
but can't seem to get any command (del, rm, or Remove-Item) to do anything. Just returns immediately without deleting the files or giving errors.
Thanks
First we can simplify your code as:
Get-ChildItem "*.milk" | Select-String -Pattern "fRating=2" | Select-Object -ExcludeProperty path | Remove-Item -Force -Confirm
The lack of action and errors might be addressable by one of two things. The Force parameter which:
Allows the cmdlet to remove items that cannot otherwise be changed,
such as hidden or read-only files or read-only aliases or variables.
I would aslo suggest that you run this script as administrator. Depending where these files are located you might not have permissions. If this is not the case or does not work please include the error you are getting.
Im going to guess the error is:
remove-item : Cannot remove item C:\temp\somefile.txt: The process cannot access the file 'C:\temp\somefile.txt'
because it is being used by another process.
Update
In testing, I was also getting a similar error. Upon research it looks like the Select-String cmd-let was holding onto the file preventing its deletion. Assumption based on i have never seen Get-ChildItem do this before. The solution in that case would be encase the first part of this in parentheses as a sub expression so it would process all the files before going through the pipe.
(Get-ChildItem | Select-String -Pattern "tes" | Select-Object -ExpandProperty path) | Remove-Item -Force -Confirm
Remove -Confirm if deemed required. It exists as a precaution so that you don't open up a new powershell in c:\windows\system32 and copy paste a remove-item cmdlet in there.
Another Update
[ and ] are wildcard searches in powershell in order to escape those in some cmdlets you use -Literalpath. Also Select-String can return multiple hits in files so we should use -Unique
(Get-ChildItem *.milk | Select-String -Pattern "fRating=2" | Select-Object -ExpandProperty path -Unique) | ForEach-Object{Remove-Item -Force -LiteralPath $_}
Why do you use select-string -pattern "fRating=2"? You would like to select all files with this name?
I think the Format-Table Path don't work. The command Get-ChildItem don't have a property called "Path".
Work this snipped for you?
$list = get-childitem *.milk | Where-Object -FilterScript {$_.Name -match "fRating=2"}
$list | foreach { rm $_.FullName }
The following code gets all files of type *.milk and puts them in $listA, then uses that list to get all the files that contain the string fRating=[01] and stores them in $listB. The files in $listB are deleted and then the number of files deleted versus the number of files that contained the match is displayed(they should be equal).
sv -name listA -value (Get-ChildItem *.milk); sv -name listB -value ($listA | Select-String -Pattern "fRating=[01]"); (($listB | Select-Object -ExpandProperty path) | ForEach-Object {Remove-Item -Force -LiteralPath $_}); (sv -name FCount -value ((Get-ChildItem *.milk).Count)); Write-Host -NoNewline Files Deleted ($listA.Count - $FCount)/($listB.Count)`n;
No need to complicate things:
1. $sourcePath = "\\path\to\the\file\"
2. Remove-Item "$sourcePath*whatever*"
I tried the answer, unfortunately, errors seems to always come up, however, I managed to create a solution to get this done:
Without using Get-ChilItem; 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 }
I tried this myself, works 100%.
I hope this helps

Powershell - "Expressions are only allowed as the first element of a pipeline"

Could someone please tell me how to avoid this error in the below circumstance?
$codegenDir = "Z:\Desktop\Song-Renamer"
$PowerShellRepresentation = dir -path $MyMusicFolder -recurse -include *.mp3,*.m4a,*.wma,*.flac,*.ape | select -ExpandProperty FullName | $codegenDir\codegen.exe -s 10 20 | Out-String | ConvertFrom-Json
What completely puzzles me is if simply omit $codegenDir (see below), the code operates correctly. I "think" I understand the concept of placing the expression first (ahead of other items in the pipeline. But I'm not sure how to rearrange/split this code so the expression in question the Codegen.exe external commandline is the first item in the pipeline (and still be able to pass data to it via pipeline).
$PowerShellRepresentation = dir -path $MyMusicFolder -recurse -include *.mp3,*.m4a,*.wma,*.flac,*.ape | select -ExpandProperty FullName | .\codegen.exe -s 10 20 | Out-String | ConvertFrom-Json
Ideally, it would be nice to do this using the least amount of code as possible.
Give the following a shot (only difference is the &):
$PowerShellRepresentation = dir -path $MyMusicFolder -recurse -include *.mp3,*.m4a,*.wma,*.flac,*.ape | select -ExpandProperty FullName | & $codegenDir\codegen.exe -s 10 20 | Out-String | ConvertFrom-Json
Here's a link to a technet article about executing commands in powershell in different ways.