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.
Related
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.
I have an extensive set of CMD scripts for an automation suite.
In a console using CMD.exe, everything works fine. After installing the Windows Creator's update, where PowerShell becomes the new default Windows shell via Explorer's menu options, my scripts break at-random. I can't provide any meaningful code for repro for two main reasons:
No obvious error is occurring; my automated scripts just hang, eventually
The halt isn't even occurring in the same place each time
What I can tell you is that the suite heavily relies on exit codes, use of findstr.exe, and type.
I know things like Windows macros, e.g., %Var% are not compatible, but I was assuming that since the only call I did was to a .bat file, .bat behavior would be the only thing I would need to worry about.
If that's not the case, should my initial .bat be triggering the direct execution of a CMD.exe instance with my parameters? If so, what's the best way to do that, PowerShell-friendly?
eryksun's comments on the question are all worth heeding.
This section of the answer provides a generic answer to the generic question in the question's title. See the next section for considerations specific to the OP's scenario.
Generally speaking, there are only a few things to watch out for when invoking a batch file from PowerShell:
Always include the specific filename extension (.bat or .cmd) in the filename, e.g., script_name.bat
This ensures that no other forms of the same command (named script_name, in the example) with higher precedence are accidentally executed, which could be:
In case of a command name without a path component:
An alias, function, cmdlet, or an external executable / PowerShell script (*.ps1) that happens to be located in a directory listed earlier in the $env:PATH (%PATH%) variable; if multiple executables by the same name are in the same (earliest) directory, the next point applies too.
In case of a command name with a path component:
A PowerShell script (*.ps1) or executable with the same filename root whose extension comes before .bat or .cmd in the %PATHEXT% environment variable.
If the batch file is located in the current directory, you must prefix its filename with ./
By design, as a security measure, PowerShell - unlike cmd.exe - does NOT invoke executables located in the current directory by filename only, so invoking script_name.bat to invoke a batch file of that name in the current directory does not work.[1]
Instead, you must use a path to target such an executable so as to explicitly signal the intent to execute something located in the current directory, and the simplest approach is to use prefix ./ (.\, if running on Windows only); e.g., ./script_name.bat.
When passing parameters to the batch file:
Either: be aware of PowerShell's parsing rules, which are applied before the arguments are passed to the batch file - see this answer of mine.
Or: use --% (the PSv3+ stop-parsing symbol) to pass the remaining arguments as if they'd been passed from a batch file (no interpretation by PowerShell other than expansion of %<var>%-style environment-variable references).
[1] eryksun points out that on Vista+ you can make cmd behave like PowerShell by defining environment variable NoDefaultCurrentDirectoryInExePath (its specific value doesn't matter).
While ill-advised, you can still force both environments to always find executables in the current directory by explicitly adding . to the %PATH% / $env:PATH variable; if you prepend ., you get the default cmd behavior.
As for your specific scenario:
After installing the Windows Creator's update, where PowerShell becomes the new default Windows shell via Explorer's menu options
This applies to the following scenarios:
Pressing Win-X (system-wide keyboard shortcut) now offers PowerShell rather than cmd in the shortcut menu that pops up.
Using File Explore's File menu now shows Open Windows PowerShell in place of Open command prompt (cmd).
However, nothing has changed with respect to how batch files are invoked when they are opened / double-clicked from File Explorer: The subkeys of HKEY_CLASSES_ROOT\batchfile and HKEY_CLASSES_ROOT\cmdfile in the registry still define the shell\open verb as "%1" %*, which should invoke a batch file implicitly with cmd /c, as before.
However, per your comments, your batch file is never run directly from File Explorer, because it require parameter values, and it is invoked in two fundamental ways:
Explicitly via a cmd console, after entering cmd in the Run dialog that is presented after pressing Win-R (system-wide keyboard shortcut).
In this case, everything should work as before: you're invoking your batch file from cmd.
Explicitly via PowerShell, using File Explorer's File menu.
Per your comments, the PowerShell console may either be opened:
directly in the directory in which the target batch file resides.
in an ancestral directory, such as the root of a thumb drive on which the batch file resides.
In both cases, PowerShell's potential interpretation of arguments does come into play.
Additionally, in the 2nd case (ancestral directory), the invocation will only work the same if the batch file either does not depend on the current directory or explicitly sets the current directory (such as setting it to its own location with cd /d "%~dp0").
This is a non-answer solution if encountering the question's specific behavior. I've verified all my halting scripts stopped halting after implementing a shim-like workaround.
As erykson said, there doesn't appear to be a reason why using a shim would be required. The goal is then to explicitly launch the script in CMD when using PowerShell, which aligns with Jeff Zeitlin's original suggestion in the question's comments.
So, let's say you're in my shoes with your own script_name.bat.
script_name.bat was your old script that initializes and kicks off everything. We can make sure that whatever was in script_name.bat is correctly run via CMD instead of PowerShell by doing the following:
Rename script_name.bat to script_name_shim.bat
Create a new script_name.bat in the same directory
Set its contents to:
#echo off
CMD.exe /C "%~dp0script_name_shim.bat" %*
exit /b %errorlevel%
That will launch your script with CMD.exe regardless of the fact that you started in PowerShell, and it will also use all your command-line arguments too.
This looks like a chicken egg problem, wihtout knowing the code it's difficult to tell where the problem is.
There are a ton of ways to start batches with cmd.exe even in win10cu.
Aliases are only a problem when working interactively with the PowerShell console and expecting behavior as it used to be in cmd.exe.
The aliases depend also on the loaded/imported modules and profiles.
This small PowerShell script will get all items from Help.exe and
perform a Get-Command with the item.
internal commands without counterparts in PoSh are filtered out by the ErrorAction SilentlyContinue.
Applications (*.exe files) are assumed identical and removed by the where clause.
help.exe |
Select-String '^[A-Z][^ ]+'|
ForEach-Object {
Get-Command $_.Matches.Value -ErrorAction SilentlyContinue
}| Where-Object CommandType -ne 'Application'|Select *|
Format-Table -auto CommandType,Name,DisplayName,ResolvedCommand,Module
Sample output on my system, all these items will likely work differently in PowerShell:
CommandType Name DisplayName ResolvedCommand Module
----------- ---- ----------- --------------- ------
Alias call call -> Invoke-Method Invoke-Method pscx
Alias cd cd -> Set-LocationEx Set-LocationEx Pscx.CD
Alias chdir chdir -> Set-Location Set-Location
Alias cls cls -> Clear-Host Clear-Host
Alias copy copy -> Copy-Item Copy-Item
Alias del del -> Remove-Item Remove-Item
Alias dir dir -> Get-ChildItem Get-ChildItem
Alias echo echo -> Write-Output Write-Output
Alias erase erase -> Remove-Item Remove-Item
Alias fc fc -> Format-Custom Format-Custom
Function help pscx
Alias md md -> mkdir mkdir
Function mkdir
Function more
Alias move move -> Move-Item Move-Item
Function Pause
Alias popd popd -> Pop-Location Pop-Location
Function prompt
Alias pushd pushd -> Push-Location Push-Location
Alias rd rd -> Remove-Item Remove-Item
Alias ren ren -> Rename-Item Rename-Item
Alias rmdir rmdir -> Remove-Item Remove-Item
Alias set set -> Set-Variable Set-Variable
Alias sc sc -> Set-Content Set-Content
Alias sort sort -> Sort-Object Sort-Object
Alias start start -> Start-Process Start-Process
Alias type type -> Get-Content Get-Content
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.
Is it possible to achieve the same effect of the Global Replace option in Visual Studio using a script ?
I need to perform some global replaces in a significant amount of files, at the present moment I am using MS Visual Studio to do a Find Replace in Files (Global Replace). Is it possible to achieve this using a script to run on a Windows machine? Will there be any implications or differences in the result using the proposed script as compared to the VS option?
I have long lost touch writing scripts hence might need some refreshers on this matter.
If you have PowerShell, it's pretty straighforward with Get-ChildItem and a script I wrote called Replace-FileString.ps1. This blog post gives an example:
http://www.wintellect.com/cs/blogs/jrobbins/archive/2012/12/10/automatically-updating-sln-files-to-vs-2012.aspx
Here's another example:
Get-ChildItem -path D:\Path -filter *.txt -recurse | Replace-FileString.ps1 -pattern 'find me' -replacement 'replace me' -overwrite
The above command would get all *.txt files in D:\Path (and subdirectories), and for each of the .txt files, it would replace the string "find me" (without quotes) with "replace me" (without quotes). The Replace-FileString.ps1 script's -overwrite parameter overwrites the original file with the file containing the replaced string.
Bill
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.)