Powershell: Define set local variable as array - powershell

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

Related

Run executable in Powershell with specific filenames as arguments

I'm trying to batch-convert heic images to png images using Powershell. What I have tried:
Get-ChildItem -Include ('*.HEIC', '*.heic') -File | & .\bin\vips.exe copy $_.Name "$(_.BaseName).png"
Pause
and
Get-ChildItem -Include ('*.HEIC', '*.heic') -File | & .\bin\vips.exe copy $_.Name ($_.BaseName + '.png')
Pause
Both times I'm getting an error VipsForeignLoad: file ".png" does not exist which tells me it treats ".png" as the first (and only) argument and ignores the object Name and Basename properties.
You're missing a $. "$($_.BaseName).png" I would use -Filter vs -Include as it is more efficient.
Edited: Try this approach bypassing the Pipe and see if you get a different result. I've also added some additional code to insure everything is fully evaluated. If this approach works you can experiment with reducing come of the $() evaluation levels.
Also are you using Linux? Some of my googling led me to believe you might be. If so you should specify this in your tags for clarity.
Clear-Host
$x = Get-ChildItem -Filter "*.HEIC" -File
ForEach ($File in $x) {
& .\bin\vips.exe copy "$($File.FullName)" $("$($File.BaseName)" + ".pdf")
}
Also note the file extension in the Filter is case insensitive so no need to repeat.
I'd also recommend adding the -Path parameter for clarity rather than assuming the default directory but that's just me.
HTH

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

Copying files defined in a list from network location

I'm trying to teach myself enough powershell or batch programming to figure out to achieve the following (I've had a search and looked through a couple hours of Youtube tutorials but can't quite piece it all together to figure out what I need - I don't get Tokens, for example, but they seem necessary in the For loop). Also, not sure if the below is best achieved by robocopy or xcopy.
Task:
Define a list of files to retrieve in a csv (file name will be listed as a 13 digit number, extension will be UNKNOWN, but will usually be .jpg but might occasionally be .png - could this be achieved with a wildcard?)
list would read something like:
9780761189931
9780761189988
9781579657159
For each line in this text file, do:
Search a network folder and all subfolders
If exact filename is found, copy to an arbitrary target (say a new folder created on desktop)
(Not 100% necessary, but nice to have) Once the For loop has completed, output a list of files copied into a text file in the newly created destination folder
I gather that I'll maybe need to do a couple of things first, like define variables for the source and destination folders? I found the below elsewhere but couldn't quite get my head around it.
set src_folder=O:\2017\By_Month\Covers
set dst_folder=c:\Users\%USERNAME&\Desktop\GetCovers
for /f "tokens=*" %%i in (ISBN.txt) DO (
xcopy /K "%src_folder%\%%i" "%dst_folder%"
)
Thanks in advance!
This solution is in powershell, by the way.
To get all subfiles of a folder, use Get-ChildItem and the pipeline, and you can then compare the name to the insides of your CSV (which you can get using import-CSV, by the way).
Get-ChildItem -path $src_folder -recurse | foreach{$_.fullname}
I'd personally then use a function to edit the name as a string, but I know this probably isn't the best way to do it. Create a function outside of the pipeline, and have it return a modified path in such a way that you can continue the previous line like this:
Get-ChildItem -path $src_folder -recurse | foreach{$_.CopyTo (edit-path $_.fullname)}
Where "edit-directory" is your function that takes in the path, and modifies it to return your destination path. Also, you can alternatively use robocopy or xcopy instead of CopyTo, but Copy-Item is a powershell native and doesn't require much string manipulation (which in my experience, the less, the better).
Edit: Here's a function that could do the trick:
function edit-path{
Param([string] $path)
$modified_path = $dst_folder + "\"
$modified_path = $path.substring($src_folder.length)
return $modified_path
}
Edit: Here's how to integrate the importing from CSV, so that the copy only happens to files that are written in the CSV (which I had left out, oops):
$csv = import-csv $CSV_path
Get-ChildItem -path $src_folder -recurse | where-object{$csv -contains $_.name} | foreach{$_.CopyTo (edit-path $_.fullname)}
Note that you have to put the whole CSV path in the $CSV_path variable, and depending on how the contents of that file are written, you may have to use $_.fullname, or other parameters.
This seems like an average enough problem:
$Arr = Import-CSV -Path $CSVPath
Get-ChildItem -Path $Folder -Recurse |
Where-Object -FilterScript { $Arr -contains $PSItem.Name.Substring(0,($PSItem.Length - 4)) } |
ForEach-Object -Process {
Copy-Item -Destination $env:UserProfile\Desktop
$PSItem.Name | Out-File -FilePath $env:UserProfile\Desktop\Results.txt -Append
}
I'm not great with string manipulation so the string bit is a bit confusing, but here's everything spelled out.

Powershell concatenating text to a variable

My source files all reside in one folder whose path is contained in a variable named $template.
I need to specify the exact filename as each file goes to a different destination.
My goal is to merely concatenate the filename to the variable.
Example:
$template = "D:\source\templatefiles\"
Filename1 is: "graphic-183.jpg"
I have tried:
Join-Path $template graphic-183.jpg
Issuing this at the cli appears to do what I want.
But now, how do I reference this concatenated file path short of creating a new variable for each file? It isn't as simple as for-nexting my way through a list as depending on the filename that determines where the file goes.
I am toying with case else, elseIf, but surely it isn't this hard.
The bottom line is, I just want to prefix the folder path to each filename and hard code the destination as it will always be the same each time the script is run.
edit
I just edited this as I forgot to mention how I am trying to use this.
In my script I intend to have lines like:
Copy-Item -Path $template filename.ext -Destination $destfolder
It's the highlighted part above that I am trying to join $template to the filename.
Thanks for any advice.
-= Bruce D. Meyer
maybe this is what you want?
you can call cmdlets in place, using parentheses, like so:
Copy-Item -Path (Join-Path $template filename.ext) -Destination $destfolder
this causes PowerShell to go from "argument mode" to "expression mode" - i.e., it returns the output of the Join-Path cmdlet as an expression.
and yes, David's and Ansgar's suggestions are also helpful - try this to get full paths only:
(get-childitem $template) | select fullname
You could build the path like this:
$template = "D:\source\templatefiles\"
Copy-Item -Path "${template}filename.ext" ...
However, I think David's suggestion might be a better solution for your problem. You could map filenames to destination folders with a hash table and do something like this:
$locations = #{
"foo" = "C:\some",
"bar" = "C:\other",
...
}
Get-ChildItem $template | % { Copy-Item $_ $location[$_.Name] }

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