Spaces in filepath - powershell

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

Related

null-valued expression error in simple powershell script

I have a small powershell script that looks in a patch for all files that contain a string and then replace that string.
Im sure this worked last week so im very confused why its now not working.
$filePath = "C:\my\file\path*"
# Get the files from the folder and iterate using Foreach
Get-ChildItem $filePath -Recurse | ForEach-Object {
# Read the file and use replace()
(Get-Content $_).Replace('oldString','NewString') | Set-Content $_
}
Im getting two errors i think, the first is:
Get-Content : Access to the path 'C:\my\file\path\YYY' is denied.
YYY is a folder in my path and im running the script as administrator, i was running as my own user who i confirmed has full access to this path.
The second is:
You cannot call a method on a null-valued expression.
Im guessing its the $_ but im really not sure. Ive tried replacing it with different name but no luck.
The You cannot call a method on a null-valued expression error is coming from calling the replace method when the result of Get-Content $_ is null.
This can happen for a few reasons:
First, inside of your ForEach-Object script block, $_ will essentially return the same value as $_.name which does not contain the full path to the folder or file being processed. Depending on the value of the working directory,$PWD, when you execute this code this could result in a bad file or folder path.
To address issues with the working directory try using $_.FullName. For debugging you can also write that value to the terminal to confirm where it is looking.
The issue could also be that the file has no contents. If the file is empty then the result of Get-Content will be $null.
Also attempting to get the content of a folder will result in an "access denied" error, after which the result of (get-content $_) will also be $null, resulting in the null error you received.
To summarize:
Double check your working directory.
Consider using $_.FullName.
Consider adding code to avoid calling Get-Content on folders.
Here is an example which I tested with several nested folders, some of which contained empty files and others which contained files with contents:
Get-ChildItem C:\testFolder -File -Recurse | ForEach-Object{
#this write host is just for debugging, to see what the file path is
Write-Host $_.FullName
$fileContents = Get-Content $_.FullName
if($fileContents -ne $null){
$fileContents.replace('oldString','newString') | Set-Content $_.FullName
}
}

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.

Expanding Environmental Variable using CSV file into Powershell script

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

Trouble editing text files with powershell Get-Content and Set-Content

Goal: Update text entry on one line within many files distributed on a server
Summary: As part of an application migration between datacenters the .rdp files on end-user desktops need to be updated to point to the new IP address of their Remote Desktop Server. All the .rdp files reside on Windows servers in a redirected folders SMB share where I have Administrative access.
Powershell experience: minimal. Still trying to wrap my head around the way variables, output and piping work.
Was originally trying to make a single line of powershell code to complete this task but got stuck and had to make script file with the two lines of code below.
-Line 1: Search for all .rdp files in the folder structure and store the full path with file name in a variable. Every file will be checked since the users tend to accidentally change file names, eliminating absolute predictability.
-Line 2: I want to make one pass through all the files to replace only instances of two particular IP addresses with the new address. Then write the changes into the original file.
$Path = ls 'C:\Scripts\Replace-RDP\TESTFILES\' -Include *.rdp -Recurse -Force -ErrorAction SilentlyContinue | foreach fullname
$Path | (Get-Content -Path $Path) -Replace 'IPserver1','newIPserver1' -Replace 'IPserver2','newIPserver2' | Set-Content $Path -Force
Have found most of the solution with Powershell but have a problem with the results. The second line of code when output to the screen changes contents correctly in memory. The content written to file however resulted in the new server IP address being written into ALL rdp files even if the source rdp file's target IP address doesn't match the -Replace criterion.
Text inside a .rdp on the relevant line is:
full address:s:192.168.1.123
changes to:
full address:s:172.16.1.23
Thank you for all assistance in reaching the endpoint. Have spent hours learning from various sites and code snippets.
You need to keep track of each file that you are reading so that you can save changes to that file. Foreach-Object makes this process easy. Inside of the Foreach-Object script block, the current object $_ is the FullName value for each of your files.
$CurrentIP1 = '192\.168\.1\.123'
$CurrentIP2 = '192\.168\.1\.124'
$NewIP1 = '172.16.1.23'
$NewIP2 = '172.16.1.24'
$files = (Get-ChildItem 'C:\Scripts\Replace-RDP\TESTFILES\' -Filter *.rdp -Recurse -Force -File -ErrorAction SilentlyContinue).FullName
$files | Foreach-Object {
if (($contents = Get-Content $_) -match "$CurrentIP1|$CurrentIP2") {
$contents -replace $CurrentIP1,$NewIP1 -replace $CurrentIP2,$NewIP2 |
Set-Content $_
}
}
Note that using the -File switch on Get-ChildItem (alias ls) outputs only files. Since -replace uses regex to do matching, you must backslash escape literal . characters.

INI editing with PowerShell

My problem is I want to change paths in INI Files wich are saved in a folder and its subfolders.
The path of the folder is C:\New\Path\.
Example INI file:
notAIniText = C:\A\Path\notAIniText
maybeAIniText = C:\A\Path\maybeAIniText
AIniText = C:\A\Path\AIniText
I read some other questions about PSini but I don't want to just id because I want to use the script on multiple PC and I don't want to install every time PSIni.
I tried:
$mabyIni = "C:\New\Path"
$AiniFile = Get-ChildItem - Path "C:\New\Path\*" -Include *.ini -Recurse
foreach ($file in $AiniFile) {
Select-String -Path $file -AllMatches "C:\A\Path\" | ForEach-Opject {
$file -replace [regex]:Escape('C:\A\Path'), ('$mabyIni')
} | Set-Content $mabyIni -Include *.ini
But this doesn't work. I tried it with Get-Content too, but that also doesn't work.
Is there any way whitout PSini?
The code in your comment is close, but just has a few syntax issues. It starts out strong:
$mabyIni = "C:\New\Path"
$AiniFile = Get-ChildItem - Path "C:\New\Path*" -include *.ini -recurse
ForEach($file in $AiniFile) {
So far, so good. You define the new path, and you get a list of .ini files in the old path, then you start to loop through those files. This is all good code so far. Then things start to go astray.
I see that you are trying to get the contents of each .ini file, replace the string in question, and then output that file to the new path with this:
(Get-Content $AiniFile.PSPath) | ForEach-Object {
$file -replace [regex]:Escape('C:\A\Path'),('$mabyIni')
}| Set-Content $mabyIni -include *.ini
Unfortunately you're using the wrong variables, and adding in an extra ForEach loop in there as well. Let's start with the Get-Content line. At this point in the script you are looping through files, with each current file being represented by $file. So what you really want to get the contents of is $file, and not $AiniFile.PSPath.
(Get-Content $file)
Ok, that got us the contents of that file as an array of strings. Now, I'm guessing you weren't aware, but the -Replace operator works on arrays of strings. Perfect, we just so happen to have gotten an array of strings! Since the Get-Content command is wrapped in parenthesis it completes first, we can actually just tack on the -Replace command right after it.
(Get-Content $file) -replace [regex]:Escape('C:\A\Path'),$mabyIni
Your -replace command that you had was super close! In fact, I have to give you props for using [regex]::escape() in there. That's totally a pro move, well done! The only issue with it is the replacement string didn't need to be in parenthesis, and it was single quoted, so it would not have expanded the string and your .ini files would have all had a line like:
AIniText = $mabyIni\AIniText
Not exactly what you wanted I'm guessing, so I removed the parenthesis (they weren't hurting anything, but weren't helping either, so for cleanliness and simplicity I got rid of them), and I got rid of the single quotes ' as well since we really just want the string that's stored in that variable.
So now we're looping through files, reading the contents, replacing the old path with the new path, all that's left is to output the new .ini file. It looks like they're already in place, so we just use the existing path for the file, and set the content to the updated data.
(Get-Content $file) -replace [regex]:Escape('C:\A\Path'),$mabyIni | Set-Content -Path $File.FullName
Ok, done! You just have to close the ForEach loop, and run it.
$mabyIni = "C:\New\Path"
$AiniFile = Get-ChildItem - Path "C:\New\Path*" -include *.ini -recurse
ForEach($file in $AiniFile) {
(Get-Content $file) -replace [regex]:Escape('C:\A\Path'),$mabyIni | Set-Content -Path $File.FullName
}