How to -Include using a variable with multiple criteria - powershell

I am trying to copy files with names that fulfills a given set of criteria from one folder to another. From this answer, I found that the -Include statement could be used for this.
My issue is that I need the criteria to be user-specified (through a variable), with the possibility of containing multiple criteria. That is, I try to do the following:
# THIS WORKS FINE
$includeFiles = "*tests.jar"
Copy-Item "from/path" "to/path" -Include $includeFiles
# THIS RETURNS NOTHING
$includeFiles = "*tests.jar, other.jar"
Copy-Item "from/path" "to/path" -Include $includeFiles
How do I work around this issue? Is it somehow possible to "destring" the variable, use another syntax or similar?

# THIS WORKS FINE
$copyPattern = "*tests.jar", "other.jar"
Copy-Item "from/path" "to/path" -Include $copyPattern
The point is that the argument to -Include is an array of strings, each string is a pattern to include. The way you wrote it would require a comma and space in the file name. So, unless you have such files, nothing would be included.
If you are positive that the pattern will never include commas and you'd rather just have a single string, you can of course convert it to an array:
$copyPatterns = "*tests.jar, other.jar" -split ', '
Note that parsing works differently in arguments to commands, which is why the following does work as well:
Copy-Item from/path to/path -Include *tests.jar,other.jar

Related

Powershell: Define set local variable as array

I have a question. I have a setup where I have a parent BAT file that invokes some subroutines I've built, and I'm trying to figure out how to get something particular to work. The short version, in my main BAT file I have this:
#echo off & setlocal
set "deleteThese = '*.PDF', '*.BMP', '*.AVI', '*.MOV', '*.PS1'
call "Subroutines\Find Folders and Remove Files of Extension.bat"
And what I'm looking for is when it invokes the second BAT file, I want it to pass the array to the environment variable. I thought I had it set up the right way to do that:
$deleteThese=$Env:deleteThese
But it's not working quite right. If I specify the deleteThese variable in the subroutine itself, it works fine; if I try to set it in the parent BAT file, it fails out and doesn't do anything. It doesn't even reach the PAUSE flag at the end of the batch file.
This is the syntax in the subroutine BAT file that actually pulls the values and clears the requested files:
$rootPath=$Env:rootPath
$searchFolder=$Env:searchFolder
$deleteThese=$Env:deleteThese
Get-ChildItem -Path $rootPath -Directory -Filter $searchFolder -Recurse | ForEach-Object {
Get-ChildItem -Path $_.FullName -File -Recurse -Include $deleteThese |
Remove-Item -Verbose
}
What am I missing? (Yes I know I could just loop it with a different file extension each time but that seems like it's overcomplicating things.)
The -Include parameter expects an array of strings, but environment variables can only contain a single string value each - so you'll need to split the value into an array before passing it to Get-ChildItem -Include.
In the calling batch file, set the variable (exactly) like this - no single-quotes, and no spaces around =:
set "deleteThese=*.PDF,*.BMP,*.AVI,*.MOV,*.PS1"
Then, in the PowerShell script, make sure to split it into an array of individual strings:
$deleteThese = $Env:deleteThese.Split(',')
Now Get-ChildItem -Include $deleteThese will work as expected

Removing the front part of a string based on an specific character. (\)

I first create my array with a list of files in a directory (and subdirectories) using the Cmdlet Get-ChildItem, and store them in a variable
$PSVariable = (Get-ChildItem -Path "F:\SQL_Backups" -Recurse *.bak).FullName
I echo the variable ($PSVariable), this is my output (as desired):
F:\SQL_Backups\INTRAPORTAL\StoreDevelopment\StoreDevelopment_backup_2021_02_11_003002_3930170.bak
F:\SQL_Backups\INTRAPORTAL\StoreDevelopment\StoreDevelopment_backup_2021_02_12_003002_4780885.bak
F:\SQL_Backups\JDASQL\DEVMOD\DEVMOD_backup_2021_02_10_190002_5130923.bak
F:\SQL_Backups\JDASQL\DEVMOD\DEVMOD_backup_2021_02_11_190003_7621021.bak
Goal:
I need to remove the directory path from each array entries so it only contains the file name that will be stored in a temporary variable within a foreach loop:
StoreDevelopment_backup_2021_02_11_003002_3930170.bak
StoreDevelopment_backup_2021_02_12_003002_4780885.bak
DEVMOD_backup_2021_02_10_190002_5130923.bak
DEVMOD_backup_2021_02_11_190003_7621021.bak
Some will recommend simply using (.Name) in the Get-ChildItem command, but I need the array to have both the path and filename (FullName) as the array's contents are being used for other parts of the function. I'm a novice when it comes to regular expressions and I can't seem to get the results in the goal section. I've even tried using trim() methods, but no luck. Any recommendations would greatly be appreciated. Thank you.
Expanding on what #AdminOfThings recommended, you are making more work for yourself than you need. PowerShell is an object based scripting language, so to succeed you should use its full POWER.
The approach you're taking now is to take only one property from this useful object and then find you need to start slicing and dicing it in order to make it work.
There's an easier way. We love easy here, and the easy way to do this is to take the full object and then pick and chose its properties where it makes sense, like this:
$i = 0
#changed to remove the .FullName at then end
$PSVariable = (Get-ChildItem -Path "F:\SQL_Backups" -Recurse *.bak)
ForEach ($item in $psVariable){
$i++
Write-host "Processing [$($item.Name)], item number $i of $($psVariable.Count)"
Copy-item -Path $item.FullName -Destination C:\temp -WhatIf
}
It gives you meaningful output and then you have the full selection of properties to work with.
The one that makes the most sense to use is just .Name as you reference above. But then you still have .FullName, which includes the qualified path as well.
If you want to see the full selection of properties, try this:
$PsVariable[0] | Format-list *
Offered only as an inferior option to that of FoxDeploy's you can also use Split-Path to get the filename from a path
$PSVariable = (Get-ChildItem -Path "F:\SQL_Backups" -Recurse *.bak).FullName
$PSVariable | Split-Path -Leaf

Removing Parts of a File Name based on a Delimiter

I have various file names that are categorized in two different ways. They either just have a code like: "866655" or contain a suffix and prefix "eu_866655_001". My hope is to write to a text file the names of files in the same format. I cannot figure out a successful method for removing the suffix and prefix.
Currently this what I have in my loop in Powershell:
$docs = Get-ChildItem -Path $source | Where-Object {$_.Name -match '.doc*'}
if ($docs.basename -contians 'eu_*')
{
Write-Output ([io.fileinfo]"$doc").basename.split("_")
}
I'm hoping to turn "eu_866655_001" into "866655" by using "_" as the delimiter.
I'm aware that the answer is staring me down but I still can't seem to figure it out.
You could do something like the following. Feel free to tweak the -Filter on the Get-ChildItem command.
$source = 'c:\path\*'
$docs = Get-ChildItem -Path $source -File -Filter "*_*_*" -Include '*.doc','*.docx'
$docs | Rename-Item -NewName { "{0}{1}" -f $_.Basename.Split('_')[1],$_.Extension }
The important things to remember is that in order to use the -Include switch, you need an * at the end of the -Path value.
Explanation:
-Filter allows us to filter on names that contain two underscores separating three substrings.
-Include allows us to only list files ending in extensions .docx and .doc.
Rename-Item -NewName supports delayed script binding. This allows us use a scriptblock to perform any necessary operations for each piped object (each file).
Since the target files will always have two underscores, the .Split('_') method will result in an three index array delimited by the _. You have specified that you always want the second delimited substring and that is represented by index 1 ([1]).
The format operator (-f) puts the substring and extension together, completing the file name.

In function repeat an action for each entered parameter

My main script run once gci on a specified drive via -path parameter , then it does multiple different tables from this output. Here below is a part of my script which does a specific table from an directory specified via -folder parameter, for example :
my-globalfunction -path d:\ -folder d:\folder
It work fine, but only for one entered folder path, the goal of this script is that user can enter multiple folders path and get a tables for each entered -folder parameter value, like this :
This clause in your Where-Object would be the issue:
$_.FullName.StartsWith($folder, [System.StringComparison]::OrdinalIgnoreCase)
The array of folders passed are most likely being cast as one long string which would never match. I had a regex solution posted but remembered a simpler way after looking at what your logic was trying to do.
Simpler Way
Even easier way is to put this information right into Get-ChildItem since it accepts string arrays for -Path. This way I don't think you even need to have 2 parameters since you never again use the results from $fol anyway. Based on the assumption that you were looking for all subfolders of $folder
$gdfolders = Get-ChildItem -Path $folder -Recurse -Force | Where-Object{$_.psiscontainer}
That would return all subfolders of the paths provided. If you have PowerShell 3.0 or higher this would even be easier.
$gdfolders = Get-ChildItem -Path $folder -Recurse -Force -Directory
Update from comments
The code you have displayed is incomplete which is what lead me to the solution that you see above. If you do use the variable $fol somewhere else that you do not show lets go back to my earlier regex solution which would work better in place with what you already have.
$regex = "^($(($folder | ForEach-Object{[regex]::Escape($_)}) -join "|")).+"
....
$gdfolders = $fol | Where-Object{($_.Attributes -eq "Directory") -and ($_.FullName -match $regex)}
What this will do is build a regex compare string with what I will assume is the logic of locate folders that begin with either of paths passed.
Using your example input of "d:\folder1", "d:\folder2" the variable $regex would work out to ^(d:\\folder1|d:\\folder2). The proper characters, like \, are escaped automatically by the static method [regex]::Escape which is applied to each element. We then use -join to place a pipe which, in this regex capture group means match whats on the left OR on the right. For completeness sake we state that the match has to occur at the beginning of the path with the caret ^ although this is most likely redundant. It would match paths that start with either "d:\folder1" or "d:\folder2". At the end of the regex string we have .+ which means match 1 to more characters. This should ensure we dont match the actual folder "d:\folder1" but meerly its children
Side Note
The quotes in the line with ’Size (MB)’ are not the proper ones which are '. If you have issues around that code consider changing the quotes.

Why two similar PowerShell copy-Item commands behave differently

I have two very similar one line commands but one works and one doesn't with variables.
Copy-Item "C:\BadSourceCodes\dropTest\base1\*" -Include "myUsers.Config","myUsers.WSF" -Destination "C:\BadSourceCodes\dropTest\temp1"
This just works I have two files and I want them to be copied over to destination folder.
If I create something like this
$files = "myUsers.Config,myUsers.WSF"
$tempFiles = [string]::Concat("`"",$files.Replace(",","`",`""),"`"")
$tempFiles
Copy-Item "C:\BadSourceCodes\dropTest\base1\*" -Include $tempFiles -Destination "C:\BadSourceCodes\dropTest\temp1" -Force
As soon as I use a variable for Include files it doesn't work. I have checked with different variations and it doesn't work.
Is the $ variable behaves differently?
I tried following variations like $($tempFiles), '$tempFiles', "$tempFiles".....
UPDATE:
I think when I am escaping the quotes and replacing , with "," then it treats quote as a part of the string. Some how it was the bad approach I took. But what worked is just $files.Split(",") which creates an array of string and Done.
The -Include argument wants an array of strings, not a list of names in a single string. Put the names in an actual array and it should work:
#create an array of strings
$files = "myUsers.Config","myUsers.WSF"
Copy-Item "C:\BadSourceCodes\dropTest\base1\*" -Include $files -Destination "C:\BadSourceCodes\dropTest\temp1" -Force