Expanding Environmental Variable using CSV file into Powershell script - powershell

i am trying to create a backup script with csv file which will contain all the local (where backup will be stored) and backup (folder to backup) loaction and run robocopy to copy files from backup to local folder. i want to import environmental variable from csv file that are used for default folders (e.g. env:APPDATA) as location to be use for backing up some files and folders in user documents or pulbic documents. When i use the env variable directly or use full path address in the script the copy action works fine.
robocopy "&env:localappdata\steam" "$backup"
but when import from csv, the script does not see it as env variable. robocopy shows the error because it picks up the locations like this
Source : C:\WINDOWS\system32\$env:LOCALAPPDATA\steam\
Dest : D:\backup\steam\
Below is the full code i am using.
$path = "$PSScriptRoot\backup\"
$locations = import-csv "$PSScriptRoot\backup\local.csv" -Delimiter "," -Header 'Local','Backup','Display' | Select-Object Local,Backup,display
foreach($location in $locations){
$source = $location.Local
$source = $source.ToString()
$destination = $location.Backup
$destination = $destination.tostring()
$Name = $location.Display
$Name = $name.tostring()
Write-host "Copying $Name, please wait...." -foregroundColor Yellow
robocopy "$destination" "$path$source" \s \e
}
And my CSV file looks like this
Steam, $env:LOCALAPPDATA\steam, Steam files Backup

As you have added a path in the csv as $env:LOCALAPPDATA\steam, and knowing that whatever you read from file using Import-Csv is a string, you need to use regex to convert the $env:VARNAME into %VARNAME% in order to be able to resolve that environment variable into a path.
Instead of
$source = $source.ToString()
do this
$source = [Environment]::ExpandEnvironmentVariables(($source -replace '\$env:(\w+)', '%$1%'))
$source wil now have a valid path like C:\Users\Aman\AppData\Local\Steam
Do the same with $destination
Regex details:
\$ Match the character “$” literally
env: Match the characters “env:” literally
( Match the regular expression below and capture its match into backreference number 1
\w Match a single character that is a “word character” (letters, digits, etc.)
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)
P.S. Instead of combining a path with string concatenation like you do in "$path$source", it is much safer to use the Join-Path cmdlet for that

Related

How do i copy files starting with HYPHEN using power shell script...?

I wrote a PowerShell script that will copy files from one location to another... But few files/documents in my source location were starting with "-" in their names, they couldn't get copied to the destination..bcoz of that hyphen symbol in starting. What change can make my code to ignore the symbol HYPHEN and make my files get copied to my destination location. Files that don't have HYPHEN in starting of their names got copied successfully. I can't replace or remove these hyphen's from the doc name it has to be there and i want to copy them.
This is the code i tried so far.
**$newstreamreader = New-Object System.IO.StreamReader("Z:\RedirectedDesktop\TextDocuments\Hyphen.txt") #MY SOURCE LOCATION THAT HAVE PHYSICAL FILES PATH
$eachlinenumber = 1
while (($readeachline =$newstreamreader.ReadLine()) -ne $null)
{
$string1="Y:\"+$readeachline #LOCATION OF PHYSICAL FILES
if (Test-Path -Path $string1){
$LogFile = "Z:\RedirectedDesktop\LogFileCheckPresent.txt" #GENERATES LOG FILE IF FILE PRESENT PHYSICALLY
$Message = $readeachline+",True";
$Message >> $LogFile;
$string2="Z:\RedirectedDesktop\Sample" #DESTINATION
$string3= Split-Path $readeachline -leaf
$string1=$string1.substring(0,$string1.LastIndexOf('\'))
Write-Host "$string1"
Write-Host "$string2"
Write-Host "$string3"
$execution =robocopy $string1 $string2 $string3;
if($execution -match 'ERROR'){
$execution | out-file "Z:\RedirectedDesktop\test_logerror.txt" -Encoding ascii -Append # CREATES A ERROR LOG FILE IF THERE IS AN ERROR IN COPYING
}
else{
$execution | out-file "\Z:RedirectedDesktop\test_log1.txt" -Encoding ascii -Append # CREATES A TEST LOG FILE IF THERE IS NO ERROR IN COPYING
}
}
else{
$LogFile="Z:\RedirectedDesktop\LogFileCheckNotPresent.txt" #GENERATES LOG FILE IF FILE NOT PRESENT PHYSICALLY
$Message = $readeachline+",False";
$Message >> $LogFile;
}
Write-Host "$eachlinenumber $readeachline"
$eachlinenumber++
}
$newstreamreader.Dispose()**
**
ERROR I GOT **
ERROR : Invalid Parameter #3 : "-2 Quote wired second company india private Limited.msg"
Simple Usage :: ROBOCOPY source destination /MIR
source :: Source Directory (drive:\path or \\server\share\path).
destination :: Destination Dir (drive:\path or \\server\share\path).
/MIR :: Mirror a complete directory tree.
For more usage information run ROBOCOPY /?
**** /MIR can DELETE files as well as copy them !
Your problem is specific to robocopy.exe:
If a directory-path or file-name argument starts with - (HYPHEN-MINUS, U+002D), it is invariably interpreted as an option (such as -MIRROR - RoboCopy accepts / and - interchangeably as option prefixes) - whether that path or name is double-quoted or not.
Workaround:
For the directory path arguments (the first two): Make the paths start with a different character, for which you have two options:
Pass a full file path (which by definition doesn't start with -).
Prepend .\ to a relative path / directory name; e.g.:
For the file name argument (the third one):
There is no direct workaround I'm aware of, only a suboptimal one that runs the risk of false positives:
Replace the initial - with wildcard character ?
While this will match your --prefixed file, it may also match other files, if their name is the same except for the first character.
Applied to your case:
robocopy $string1 $string2 ('?' + $string3.Substring(1))

Why does my powershell script loop not work when run, but when stepped through in debugger it works fine?

So I have the below script for a project at work:
# This script will look for a CSV in the script folder
# If found it will split the CSV based on a change in a column header
# It will then create seperate CSV files based on the column data
# get script directory and add it to a variable
Set-Location $PSScriptRoot
$baseDir = $PSScriptRoot
# get a list of csv file names
$csvfile = Get-ChildItem -Path $baseDir -Filter *.csv
# If multiple CSV files loop through all of them
foreach ($i in $csvfile) {
# Import and split the original csv
# Change the value after -Property to match the column header name of the column used to split on value change -
# Header names with spaces require surrounding quotes -
# This value will also be used to name the resulting CSV file
Import-Csv $i | Group-Object -Property "Submission ID" |
Foreach-Object {$path="Output\"+$_.name+".csv" ; $_.group |
Export-Csv -Path $path -NoTypeInformation}
# get the current time and date
$procdte = Get-Date -uformat "%m-%d-%Y %H %M %S"
# rename the original file so that it's not processed again
Rename-Item $i -NewName "Processed $procdte.txt"
}
# End processing loop
Important: some parts of this script are commented out - like the Rename-Item line is half-commented. Dunno why, think it's a stackoverflow markdown issue. It isn't like that in ISE
I tested it out with two csv files in the current directory. It's supposed to rename both files at the end to a .txt so they don't get processed again. When I just run the script, it misses the 2nd csv file. But when I step through it with debugger in ISE, it renames both files fine. What gives?
Ran powershell script, expecting both CSV files to be renamed, however it only renamed one of them. Thought there was an issue with the script, but when I run it in debug, it renames both files fine. Have managed to replicate multiple times.
Edit: to specify - this is not designed to work with two csv files specifically. It's supposed to work with one or more. I was just using two to test.

Powershell pattern matching

I have a requirement to create directories based on a numeric 9 digit with leading zeros primary key from an order entry database on a remote server.
Each morning a new directory should be created. The directory name will be the primary key number of the new customer. I already have the sql statement extracting the primary key information into a text file using BCP that preserves the leading zeros.
This file is transferred from the database server to the local directory where the folders need to be created. Using some PowerShell code that I think I found I am trying to create the folders from the text file that I have been modifying.
I need the leading zeros preserved in the folder name so I can reference back to the database later in the project. My problem is that when I run the PowerShell script, no folders are created. I think I have the problem isolated to the pattern definition, but don't understand what is wrong.
Input txt file example
001132884
001454596
001454602
001454605
001454606
001454601
001107119
001454600
001454608
PowerShell script
$folder="Customerdocuments"; # Directory to place the new folders in.
$txtFile="E:\dirtext.txt"; # File with list of new folder-names
$pattern="\d+.+"; # Pattern that lines must match
Get-Content $txtFile | %{
if($_ -match $pattern)
{
mkdir "$folder\$_";
}
}
I'm missing a clear question/error report.
Your pattern takes all digits (greedy) from input \d+
and requires at least one other (any) character .+ which isn't present in your sample file.
So your issue isn't related to leading zeroes.
Better specify exactly 9 digits and put that into a Where-object,
The path from $folder will be relative to the current folder and should be build using Join-Path
As mkdir is just a wrapper function for New-Item and it supports piped input I'd use it directly.
$folder="Customerdocuments"; # Directory to place the new folders in.
$txtFile="E:\dirtext.txt"; # File with list of new folder-names
$pattern="^\d{9}$" # Pattern that lines must match
Get-Content $txtFile | Where-Object {$_ -match $pattern}|
New-Item -Path {Join-Path $folder $_} -ItemType Directory | Out-Null
}

Batch copy and rename files with PowerShell

I'm trying to use PowerShell to batch copy and rename files.
The original files are named AAA001A.jpg, AAB002A.jpg, AAB003A.jpg, etc.
I'd like to copy the files with new names, by stripping the first four characters from the filenames, and the character before the period, so that the copied files are named 01.jpg, 02.jpg, 03.jpg, etc.
I have experience with Bash scripts on Linux, but I'm stumped on how to do this with PowerShell. After a couple of hours of trial-and-error, this is as close as I've gotten:
Get-ChildItem AAB002A.jpg | foreach { copy-item $_ "$_.name.replace ("AAB","")" }
(it doesn't work)
Note:
* While perhaps slightly more complex than abelenky's answer, it (a) is more robust in that it ensures that only *.jpg files that fit the desired pattern are processed, (b) shows some advanced regex techniques, (c) provides background information and explains the problem with the OP's approach.
* This answer uses PSv3+ syntax.
Get-ChildItem *.jpg |
Where-Object Name -match '^.{4}(.+).\.(.+)$' |
Copy-Item -Destination { $Matches.1 + '.' + $Matches.2 } -WhatIf
To keep the command short, the destination directory is not explicitly controlled, so the copies will be placed in the current dir. To ensure placement in the same dir. as the input files, use
Join-Path $_.PSParentPath ($Matches.1 + '.' + $Matches.2) inside { ... }.
-WhatIf previews what files would be copied to; remove it to perform actual copying.
Get-ChildItem *.jpg outputs all *.jpg files - whether or not they fit the pattern of files to be renamed.
Where-Object Name -match '^.{4}(.*).\.(.+)$' then narrows the matches down to those that fit the pattern, using a regex (regular expression):
^...$ anchors the regular expression to ensure that it matches the whole input (^ matches the start of the input, and $ its end).
.{4} matches the first 4 characters (.), whatever they may be.
(.+) matches any nonempty sequence of characters and, due to being enclosed in (...), captures that sequence in a capture group, which is reflected in the automatic $Matches variable, accessible as $Matches.1 (due to being the first capture group).
. matches the character just before the filename extension.
\. matches a literal ., due to being escaped with \ - i.e., the start of the extension.
(.+) is the 2nd capture group that captures the filename extension (without the preceding . literal), accessible as $Matches.2.
Copy-Item -Destination { $Matches.1 + '.' + $Matches.2 } then renames each input file based on the capture-group values extracted from the input filenames.
Generally, directly piping to a cmdlet, if feasible, is always preferable to piping to the Foreach-Object cmdlet (whose built-in alias is foreach), for performance reasons.
In the Copy-Item command above, the target path is specified via a script-block argument, which is evaluated for each input path with $_ bound to the input file at hand.
Note: The above assumes that the copies should be placed in the current directory, because the script block outputs a mere filename, not a path.
To control the target path explicitly, use Join-Path inside the -Destination script block.
For instance, to ensure that the copies are always placed in the same folder as the input files - irrespective of what the current dir. is - use:
Join-Path $_.PSParentPath ($Matches.1 + '.' + $Matches.2)
As for what you've tried:
Inside "..." (double-quoted strings), you must use $(...), the subexpression operator, in order to embed expressions that should be replaced with their value.
Irrespective of that, .replace ("AAB", "") (a) breaks syntactically due to the space char. before ( (did you confuse the [string] type's .Replace() method with PowerShell's -replace operator?), (b) hard-codes the prefix to remove, (c) is limited to 3 characters, and (d) doesn't remove the character before the period.
The destination-location caveat applies as well: If your expression worked, it would only evaluate to a filename, which would place the resulting file in the current directory rather than the same directory as the input file (though that wouldn't be a problem, if you ran the command from the current dir. or if that is your actual intent).
In Powershell:
(without nasty regexs. We hates the regexs! We does!)
Get-ChildItem *.jpg | Copy-Item -Destination {($_.BaseName.Substring(4) -replace ".$")+$_.Extension} -WhatIf
Details on the expression:
$_.BaseName.Substring(4) :: Chop the first 4 letters of the filename.
-replace ".$" :: Chop the last letter.
+$_.Extension :: Append the Extension
Not Powershell, but Batch File:
(since someone wants to be ultra-pedantic about comments)
#echo off
setlocal enabledelayedexpansion
for %%a in (*.jpg) do (
::Save the Extension
set EXT=%%~xa
::Get the Source Filename (no extension)
set SRC_FILE=%%~na
::Chop the first 4 chars
set DST_FILE=!SRC_FILE:~4!
::Chop the last 1 char.
set DST_FILE=!DST_FILE:~,-1!
:: Copy the file
copy !SRC_FILE!!EXT! !DST_FILE!!EXT! )
try this:
Get-ChildItem "C:\temp\Test" -file -filter "*.jpg" | where BaseName -match '.{4,}' |
%{ Copy-Item $_.FullName (Join-Path $_.Directory ("{0}{1}" -f $_.BaseName.Substring(4, $_.BaseName.Length - 5), $_.Extension)) }

Spaces in filepath

Working on a simple script to pull workstation names from a .csv file then open a folder location on that workstation. I keep running into trouble on how to get PowerShell to not split the filepath. So far I have tried:
Single quotes: '\\$results\c$\direc\Desktop\Start Menu\Programs\Startup'
Regular quotation: "\\$results\c$\direc\Desktop\Start Menu\Programs\Startup"
Double quotes: ""\\$results\c$\direc\Desktop\Start Menu\Programs\Startup""
Backtick in front of the space: "\\$results\c$\direc\Desktop\Start` Menu\Programs\Startup"
8.3 name: "\\$results\c$\direc\Deskto~1\StartM~1\Progra~1\Startu~1"
Here is my code:
$inputFile = "C:\Users\$env:username\Desktop\workstations.csv"
$results = #()
Import-CSV -Path $inputFile -Header Workstations | % {
Invoke-Item -Path "\\$results\c$\JHMCIS\Desktop\Start Menu\Programs\Startup"
}
Everything works perfect until it reads the file path. It then kicks back an error that says the path does not exist.
Your string formatting is fine, the problem is that you just created an empty array named $results and then are adding that to the string when you do your invoke. change your last line to
% {Invoke-Item -Path "\\$($_.Workstations)\c$\JHMCIS\Desktop\Start Menu\Programs\Startup"}
Note that the above assumes that the file has no headings and only a single column that you are defining the name of using the -header param on your Import-CSV