PowerShell: Directory Retrieval and syntax error - windows

A little background...I use Windows XP, Vista, and 7 quite frequently. As such, I constantly have to move my program settings from the %appdata% folder on each PC to the next. I figured that making a PowerShell script to do this for me and remove the folders after I finish would be something to ease my troubles. As I generally have my work on a flash drive, I was hoping to use relative paths, but it seems to be causing me a bit of trouble, but the biggest problem is that I don't seem to understand Powershell enough to know what mistake I'm making and how to fix it... So I came here.I figured that I could separate the task into two scripts; one for placing the directories and the second for copying them back to the original folder and removing any trace of them behind. I'll show you want I have so far. I figured retrieving them might be more difficult so I started there. Here's what I have so far. I'm using a txt file to make it easy to update the list of folders I want or need transferred so it's also being targeted by a variable.
$fldrtxt = Get-Content .\FolderList.txt
$dirget = -LiteralPath ="'%appdata%'\$_fldertxt"
$dirpost = "./Current"
# get-command | Add-Content .\"$today"_CommandList.txt
Set-Location c: {get-content $_dirget} | %{ copy-item $_dirpost}
I can't get PowerShell to recognize the same command that I use when I use the run utility. Since I'm sure I can use %appdata% to reference where I want the folders taken from and to, how can't I write this script to do what I want? I can use an absolute path, because I'd have to use a separate script for all three computers. And that I don't want.
How can I use PowerShell to do what I want and target the folders I need to use?

First: Accerss the Environment
Since I'm sure I can use %appdata% to reference where I want the folders take from and too
Wrong syntax for PowerShell, the %var% syntax for environment variables is specific to cmd scripts (and carried forward from MS-DOS batch files).
In PowerShell to access environment variables prefix their name with env:, so $env:AppData.
$_dirget = "$env:AppData\$_fldertxt"
Second: Passing parameters
Don't include the parameter name in the variable, a variable passed to a cmdlet will be passed as an argument not a parameter name. You need:
get-content -LiteralPath $_dirget
(There is something call "splat" that allows you to use a hash tables of parameter name-argument pairs as a hashtable, but that's unnecessary here.)

Related

Best method to automate across platforms

On a daily basis I edit G code to be run on a CO2 laser and I must remove the same string from every file before running it. Currently I open each file in notepad and do ctrl + h and replace it that way. I am looking for a more efficient way to do it.
I am given a packet with a run number (ie 123456), and that run number corresponds to a directory on the network. In that directory are a number of .txt or .nc files that require the string to be removed.
Each run directory is contained in a file structure that looks like this
"\\server\x\companyname\123456"
In \companyname\ there could be hundreds of run directories, all 6 digits - these are the run numbers and are different for each case. I would like to be able to create a program that prompts me for the the run number, and then finds that directory in \x, or rather the complete path as \companyname changes based on the run number, and then replace the string in each file within the \123456 directory. So if the string is Y11 and I was given the run number 000001, I would be prompted for the run number, it would search for the directory and set the path to \\server\x\walmart\000001\ and search each file in this directory for the string Y11 and delete it.
I tried using powershell and was able to find the run folder using Prompt and get-childitem using -recurse and setting a prompted variable to use for -include, however the system doesn't allow scripts!
I can't run scripts because it's a work computer. It says the execution of scripts are disabled on this system. Basically get-executionpolicy is set to restricted and I don't have a cert for this.
This is the code i have so far
$Run = Read-Host prompt "What is the run number?"
$Files = Get-ChildItem -Path "\\\server\x\" -Filter "$Run" -recurse
foreach ($file in $files)
(Get-object $file.PSPath) |
Foreach-Object {$_ -replace "G33", ""} |
Set-Content $file.PSPath
The last portion starting with foreach is something I copied and pasted off a previous example. I have very little experience with powershell. So the run number is located in a directory two levels below x\, for and when the user is prompted for the run number I want it to search all of x, locate the path including the directory that contains the run number (walmart in the example above), and then get all the files in the run number folder. Those files would have a path like
\server\x\walmart\000001\118000001_01.NC
There can be many files in this run folder, and they all need to have the string G33 removed from them.
I am new to this, can someone please explain step by step using powershell, visual basic or any method you think would be the easiest? Even java! I am hoping to be able to use this across PCs with different operating systems. Mostly windows. Thanks!
One possible solution is having an administrator for that server run PowerShell's Set-ExecutionPolicy RemoteSigned commandlet, so that local PowerShell scripts can be executed. Note that on Windows 64-bit, this might need to be done from both a 64-bit PowerShell prompt and from a 32-bit PowerShell prompt, to ensure local PowerShell scripts could be executed from either PowerShell.
Another approach is to use sed (short for Stream Editor), a classic Unix utility that is available on Linux, macOS and Windows. For the latter, search Google for UnixUtils on SourceForge, which includes sed and several other classic Unix utilities built for Windows. Sed can do global search-and-replace within text files. It is non-interactive and meant to be executed from a script (or Batch File).
A VBScript can also read each text file and do search-and-replace. You can open the text file and using a loop read each line. If a line contains the search string, replace that text (optionally with nothing) and then write it; otherwise write it unmodified. You write to a temp file in the same path as the file you're reading, and when done you delete the original and rename the new temp file to the original file name. You have to do error checking all along the way.
As for locating the sub-folders by Run Number, you can probably do that with a batch file (or bash script on Linux and macOS). Something like this:
#echo off
:get-directory-list
dir \\server\x /S /B /AD /OGNE > "C:\Users\Me\Desktop\ListOfDirectories.txt"
:prompt-runnumber
SET RUNNUMBER=
SET /P RUNNUMBER= Enter the 6-digit run number:
if not defined RUNNUMBER echo invalid input & goto prompt-runnumber
if /i "%RUNNUMBER%" EQU "q" goto :EOF
:get-dir-for-run-number
type "C:\Users\Me\Desktop\ListOfDirectories.txt" | find "%RUNNUMBER%" > nul
if ERRORLEVEL 1 echo Run Number not found & goto prompt-runnumber
type "C:\Users\Me\Desktop\ListOfDirectories.txt" | find "%RUNNUMBER%" > %TEMP%\%~n0.tmp"
set /P RUNFOLDER= < "%TEMP%\%~n0.tmp"
echo Run Folder full path is %RUNFOLDER%
:process-files-in-folder
for %%i in (%RUNFOLDER%\*.*) do (
echo Processing folder %RUNFOLDER%, file %%i
rem ... run sed, VBscript, etc. to edit file
)
You might have to edit the above batch file to use a mapped drive letter. For bash, you'd have to first mount the network share and refer to the mount point "directory".
bash offers corresponding commands to list directories, sort them, search for text strings (using grep rather than find), prompting for input, reading input from text files, etc. Both Windows Batch and bash support sub-routines (batch files can call themselves and pass a label as a parameter) if needed.

how to make a Powershell script avaiable from anywhere

I have a Powershell script I would like to make "public", meaning I want to be able to execute the script from any folder as you can do from the command prompt.
Is this possible?
You can also add an alias in your local powershell profile
Alias Example in profile
Set-Alias hello C:\scriptlocation\script.ps1
Now anytime you type hello, the script.ps1 will run.
More info on the various profiles that the alias can be saved to.
https://devblogs.microsoft.com/scripting/understanding-the-six-powershell-profiles/
You can explore the use of your powershell profile to achieve this. See: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles.
If the script is just a function or some variables, you can copy and paste the content into your profile.
Alternatively, if the script represents a standalone unit of code you want to keep separate, you could import it into your main profile as such:
get-content -path C:\blahblahblah\scriptName.ps1 -raw | invoke-expression
Finally, if you are writing an "advanced" powershell function, or are trying to do things officially, you could investigate the use of powershell modules as a way to export your function. See: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_modules
#Lee_Dailey's answer of adding the script to your path is also viable. If you want to add a lot of scripts, one way to do that is to add a folder like C:/PowershellScripts/, to your path, save your scripts there, and then you'll be able to invoke your .PS1 file from anywhere.
Name the script something meaningful ie: awesome.bat Save it in a dir and add the dir to windows env. Command awesome will be globally available.

Powershell get case sensitive path as displayed in windows

I'm looking for a way to get the my local path that includes any camel cases that are used in the names. The primary reason is so I can use the same path to make a call in windows wsl. For example, in windows I can call a file as
c:\FoO\bar.txt
c:\Foo\Bar.txt
and windows will display it as c:\foo\bar.txt. When I try to enter the WSL with bash I need to know the actual path since Linux is case sensitive.
I have tried using
$PSScriptRoot
Split-path C:\FoO\Bar.txt
(get-Item c:\Foo\Bar.txt).FullName
but they will only provide the path that to call the script.
How do I get my path as it's displayed in the windows os?
I can't just call the full path of the file I need since I can't guarantee the root directory it starting from. I also don't want to burn up cycles doing a find.
What you want is to look at the Target property you get back from Get-Item. Fullname will come back however you typed it initially, but Target is actually a code property that seems to get the raw path of the object.
(get-Item c:\Foo\Bar.txt).Target
Find the directory with Windows File Explorer and it shows full path name.
Find the directory in WSL or PowerShell and "pwd" or "echo $PWD" gives full path name. Add directory to PATH in $HOME/.profile and you don't need full path name.
I have some automated ways to do it.
See http://ContextKnowledge.blog
"myenv package for Windows 10 + Cygwin + WSL/Ubuntu"
The package includes a few short shell scripts
which find the information and record it in environment variables.
If these shell scripts don't work for you, contact me with more details
and I'll figure out something that will.

%~dp0 equivalent in powershell (using Expand-Archive cmdlet)

I'm pretty new to scripting (especially powershell) and new to Stack Overflow, so please, excuse my ignorance and please bear with me! I will do my best to specifically explain what I'm looking to do and hopefully someone can give a detailed response of what I could do to make it work..
Intended Process/Work Flow: A co-worker downloads "Install.zip" file that has all the necessary files. This "Install.zip" file contains "Setup.bat" file (for computer config), "Fubar.zip" file, 2 powershell scripts, and a custom powerplan (.pow) file. Once downloaded they will run the "Setup.bat" file and it will pretty much do all the work. Inside that batch file it calls 2 powershell scripts. 1)"Download.ps1" - Downloads some other files from the web. 2.) "Unzip.ps1" - Unzips "Fubar.zip" and places contents in another folder - C:\TEST\
Issue: I've recently gotten familiar with using %~dp0 in batch files. I want to make sure that the location where my co-worker initially downloads the Install.zip doesn't throw off my batch file. So for example.. some people will download .zip files to the "Downloads" folder, then extract contents to proper destination. Others will download the .zip to a specific folder, then extract it within that folder. [Ex: C:\Alex\Install.zip --Extraction-- C:\Alex\Install\((Content))] So I tried to not pre-define file locations due to the variables. I've gotten the %~dp0 to work everywhere I need it to in my batch file. The only issue I have is getting my powershell scripts to use same working directory that my batch file is in. *My batch file and my powershell scripts will always be in the same directory. (Wherever that may be)
Goal: I want my powershell script ("Unzip.ps1") to look for my "Fubar.zip" file in the same directory that its currently running in. (Again - Wherever that may be) I basically want to remove any variables that may throw off the powershell script. I want it to always use it's current working directory to locate Fubar.zip. I basically need powershell to either use its current working directory OR figure out a way to have it pull its current working directory and use that to look for "Fubar.zip".
my current "Unzip.ps1" powershell script is extremely basic.
Unzip.ps1:Expand-Archive -Force c:\ALEX\Install.zip\Fubar.zip -dest c:\TEST\
Batch File Command that calls the Unzip.ps1 script: Powershell.exe -executionpolicy remotesigned -File %~dp0UNZIP.ps1
Please keep in mind, I'm just learning scripting and I'm teaching myself. My knowledge is limited, but I've made it this far and this is the only part I'm stuck on. Please give clear responses. Any help or advice would be extremely appreciated! Using PowerShell 5.0
Thanks in advance!
The equivalent of cmd.exe's %dp0 in PowerShell v3+ is $PSScriptRoot:
Both constructs expand to the absolute path of the folder containing the batch file / PowerShell script at hand.
(The only difference is that %dp0 contains a trailing \, unlike $PSScriptRoot).
Thus, to make your Unzip.ps1 script reference Fubar.zip in the same folder that the script itself is located in, use:
Expand-Archive -Force $PSScriptRoot\Fubar.zip -dest c:\TEST\
Constructing the path as $PSScriptRoot\Fubar.zip - i.e., blindly joining the variable value and the filename with \ - is a pragmatic shortcut for what is more properly expressed as (Join-Path $PSScriptRoot Fubar.zip). It is safe to use in this case, but see the comments for a discussion about when Join-Path should be used.
The automatic $PSScriptRoot variable requires PS v3+; in v2-, use
Expand-Archive -Force ((Split-Path $MyInvocation.Mycommand.Path) + '\Fubar.zip') -dest c:\TEST\
From your description, I gather that Fubar.zip and Unzip.ps1 are in the same directory. We'll pretend this directory is C:\Users\Me\Temp; although I understand that may vary.
Powershell's working directory will be the directory you're in (if called from CMD) when you launch; otherwise, it'll be from $env:UserProfile. Since the .bat file always call Unzip.ps1 from the directory that it's in (C:\Users\Me\Temp), powershell.exe will find it with this command (you can still use %~dp0 here; it's not hurting anything):
powershell.exe -ExecutionPolicy RemoteSigned -File Unzip.ps1
Inside of Unzip.ps1, you'll use Get-Location:
Expand-Archive -Force "$(Get-Location)\Fubar.zip" -dest c:\TEST\
However, if the .bat file does a cd into another directory, this won't work. From your %~dp0UNZIP.ps1 example, I assume this isn't the case, but let's address it anyway. If this is the case, you need to process from where the location of the script is. So for this call the full/relational path to the .ps1:
powershell.exe -ExecutionPolicy RemoteSigned -File C:\Users\Me\Temp\Unzip.ps1
Then, your Unzip.ps1 will need to look like this:
Expand-Archive -Force "${PSScriptRoot}\Fubar.zip" -dest 'C:\TEST\'
Alternatively, you can also do some fancy path splitting, as #JosefZ suggested. The $PSCommandPath and $MyInvocation variables contain the full path to your script; which you should familiarize yourself with:
$location = Split-Path $PSCommandPath -Parent
$location = Split-Path $MyInvocation.MyCommand.Path -Parent
Expand-Archive -Force "${location}\Fubar.zip" -dest 'C:\TEST\'
Note: Of course, you wouldn't set $location twice. I'm just showing you two ways to set it.
I hope this helps!
Prior to Powershell 3
$currentScriptPath = Split-Path ((Get-Variable MyInvocation -Scope 0).Value).MyCommand.Path
Otherwise, $PsScriptRoot will work. If you're going to depend on it, though, I'd make sure you mark the script with #Requires -version 3
I'd advise against changing the PWD unless you must. Which is to say, reference the variable directly.

Powershell issue when executing .ps1 script

So, warning, this is probably a really newbie questions, so apologies in advance.
I'm starting to learn Powershell and one of the first things I want to do, is just make a directory & copy a file to it.
Now, if I use the following commands in a CMD window, they work perfectly.
mkdir %HOMEPATH%\test
cp test.txt %HOMEPATH%\test
However, when I put them into a .ps1 file and execute it, I get an error saying the directory could not be found etc (see below)
Copy-Item : Could not find a part of the path 'C:\Chef\windowsdevbox-master\%HOMEPATH%\.berkshelf'
Now, I got told that this is because I need to put CMD before each command. I ran this, with the CMD in front of each one and the error disappeared and instead I was presented with the "Home text" for CMD and the script finished.
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
However, the folder was not created and the file was not copied over.
I just wondered what I need to do to get this to work.
In PowerShell mkdir is a built in function designed to emulate the same functionality, but using the built in cmdlet New-Item to do the underlying work.
cp is a straight up alias to PowerShell's Copy-Item cmdlet.
You don't need to precede these with cmd to make them work.
PowerShell does not accept the %VAR% syntax for environment variables though. Instead you would use the special $env variable, followed by a colon : followed by the variable name: $env:HOME.
mkdir $env:HOMEPATH\test
cp test.txt $env:HOMEPATH\test
%HOMEPATH% is not a PowerShell variable.
Environment variables are stored in the $env variable scope. You can access it with $env:homepath.
Here, I would use:
mkdir "${env:homepath}\test";
cp test.txt "${env:homepath}\test";
I might be inclined to use mkdir "${env:homedrive}${env:homepath}\test";, but I don't really know what you're trying to accomplish.
The curly braces here tell PowerShell that the entire contents are the variable name. The colon tends to confuse it, IMX, especially when you embed variables in strings.
Environment variables are special in PowerShell. They have their own provider. You can list them with Get-ChildItem env:, and manipulate them at the env: PSDrive.
Additional Note: Certain configurations might have unpredictable results because the strings might have too many backslashes or have backslashes in the wrong place. In this case, you might need to use Join-Path to combine the paths correctly.
Say:
$env:homedrive is 'U:'
$env:homepath is '\'
And the subfolder is '\test'
Then "${env:homepath}\test' is \\test, which looks like a UNC path. Instead you can use Join-Path ${env:homepath} '\test', which will then correctly create '\test'.
If you want to join three things, it's a bit complex:
Join-Path ${env:homedrive} (Join-Path ${env:homepath} '\test')
But that correctly creates 'U:\test' instead of 'U:\\test'.
You can also use the -Resolve option to convert a relative path to the absolute one.

Resources