Cannot load SharePoint powershell cmdlets in visual studio pre-deployment script - powershell

While working on a SharePoint 2010 solution in Visual Studio 2012, I want to run a PowerShell script to remove a lookup field from one of my lists, allowing Visual Studio to automatically resolve the script deployment conflict by deleting the existing list and replacing it with the one in my solution.
However, when running PowerShell from the project's "Pre-deployment Command Line" in Visual Studio, when my script attempts to use get-spweb, PowerShell reports that the object is not found. Scrolling upward in the Visual Studio's output window, I see that Add-PsSnapin Microsoft.SharePoint.PowerShell is reporting various problems:
(Note: actual error message has the expanded $(ProjectDir) value rather than "$(ProjectDir)" as text. Trying various levels of indirection to ensure I'm using the correct, 64-bit version of PowerShell does not change this working directory, nor does using cd or set-location commands prior to calling my script make any difference in this directory. I'm wondering if an invalid working directory is part of the problem...)
The local farm is not accessible. Cmdlets with FeatureDependencyId are not registered.
Could not read the XML Configuration file in the folder CONFIG\PowerShell\Registration\.
Could not find a part of the path '$(ProjectDir)\CONFIG\PowerShell\Registration'.
No xml configuration files loaded.
Unable to register core product cmdlets.
Could not read the Types files in the folder CONFIG\PowerShell\types\.
Could not find a part of the path '$(ProjectDir)\CONFIG\PowerShell\types'.
"No Types files Found."
Could not read the Format file in the folder CONFIG\PowerShell\format\.
Could not find a part of the path '$(ProjectDir)\CONFIG\PowerShell\format'.
No Format files Found.
Running the PowerShell script directly from a Command Prompt window works fine, the SharePoint snapin loads correctly, but running from Visual Studio always fails. Previous research indicated potential problems with SharePoint and SQL Server permissions, yet my account has full admin in both. Visual Studio is running "as administrator". Research also turned up possible problems with 32-bit vs 64-bit. My Pre-deployment Command Line now calls %comspec% /c to ensure 64-bit.
Pre-deployment Command Line:
%comspec% /c ""$(ProjectDir)PowerShell Scripts\predeployment-command.cmd" "$(SharePointSiteUrl)" "$(ConfigurationName)" "$(ProjectDir)""
*.cmd file:
echo off
rem pre-deployment script
rem call from pre-deployment command line like
rem %comspec% /c ""$(ProjectDir)PowerShell Scripts\predeployment-command.cmd "$(SharePointSiteUrl)" "$(ConfigurationName)" "$(ProjectDir)""
rem
echo Running "predeployment-command.cmd" file
echo
echo %%1 = %~1
echo %%2 = %~2
echo %%3 = %~3
echo
cd "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14"
cd
echo on
powershell.exe -file "%~3PowerShell Scripts\predeployment-script.ps1" -targetWeb "%~1" -CONFIG "%~2"
PowerShell script file:
<#
.SYNOPSIS
Pre-deployment script for Visual Studio development deployments
add command to run this script in the project's pre-deployment command-line box
.EXAMPLE
powershell .\dev-predeployment-script.ps1
#>
param(
[string] $targetWeb = $(throw "Please specify the site to which Visual Studio is deploying!"),
[string] $CONFIG = $(throw "Please specify the active configuration!")
)
write-host "Running Pre-Deployment PowerShell Script";
# Ensure SharePoint extensions are loaded
$snapin = $(Get-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction "SilentlyContinue");
if($snapin -eq $null) {
Add-PsSnapin Microsoft.SharePoint.PowerShell;
}
$ErrorActionPreference = 'Stop';
#echo back parameter values:
write-host "TargetWeb = $targetWeb"
write-host "Configuration = $CONFIG"
#get web-site
$web = get-spweb $targetWeb;
if($web -ne $null) {
$recipients = $web.lists["Recipients"];
if($recipients -ne $null) {
$lookupField = $recipients.Fields["Request ID"];
if($lookupField -ne $null) {
$recipients.fields.remove($lookupField);
}
}
}
I'm developing on a 64-bit Windows Server 2008 r2 virtual machine.

Hi I know this was from a billion years ago, but I just struggled with and solved the same problem, so posting here in case others run into it.
Two problems are happening and fighting with each other:
Problem #01: Spaces in paths: At first glance I see a lot of 'em.
Make your life easier and get rid of these (if/when possible).
Examples:
"$(ProjectDir)PowerShell Scripts"
"C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14"
"$(SharePointSiteUrl)" "$(ConfigurationName)" "$(ProjectDir)" <-- This is the problem!!!
maybe others too? you've got multiple levels there that's hard to visualize.
Call chain is (I think?):
LVL1: %comspec% /c
LVL2: predeployment-command.cmd
LVL3: "%~3PowerShell Scripts\predeployment-script.ps1"
which you wish to be: '$(ProjectDir)PowerShell Scripts\predeployment-script.ps1'
but is likely rendering as: 'path with spaces"PowerShell Scripts\predeployment-script.ps1'
Problem #02: Visual Studio Paths always end in backslash "\" (which I hilariously have to escape so that the SO Markdown will display it). BATCH Expansion Escapes the next character. So if you've correctly wrapped quotes around a "path with spaces", but the "path with spaces ends in backslash\", then you end up with "...\" being escaped to %path%" with a trailing or hanging quote " at the end
Visual Studio $(ProjectDir) - with spaces!
if you DO NOT include the quotes, then spaces in path will break it. if you DO include quotes then it gets escaped.
Solution is to include an extra slash at the end, resulting in double backslash \\ which escapes down to single backslash '\'... whew!
Here's my working version if you'd like an example to work off of:
https://stackoverflow.com/a/73409081/738895
Final note:
IF all you need is to ensure (32-bit vs 64-bit), this can be done via the Configuration Manager in Visual Studio
You can google "Visual Studio force build as x86" if you need via msbuild/commandline or follow these instructions
https://learn.microsoft.com/en-us/visualstudio/ide/how-to-configure-projects-to-target-platforms?view=vs-2022

Related

Format PS1 to be Double-Clicked [duplicate]

I have my_project.ps1 file from which I am activating virtual environment & starting my project.
currently I need to open my powershell then after I need to go to directory where I have saved my .ps1 file & have to open it from powershell only.
Is there any way so that I can double click on .ps1 file & it will open automatically in power shell ?
By design, double-clicking (opening) *.ps1 files from the Windows [GUI] shell (in this case: Desktop, File Explorer, and the taskbar, via pinned items) does not execute them - instead they're opened for editing in Notepad or in the PowerShell ISE, depending on the Windows / PowerShell version.
However, since at least Windows 7, the shortcut menu for *.ps1 files contains a Run with PowerShell command, which does invoke the script at hand; this may be enough for your purposes, but this invocation method has limitations - see the bottom section for details.
If you do want to redefine double-clicking / opening so that it executes *.ps1 scripts, you have two options:
Note:
For a given script (as opposed to all .ps1 files), you may alternatively create a shortcut file or batch file that launches it, but that isn't a general solution, as you'd have to create a companion file for each and every .ps1 file you want to run by double-clicking. It does, however, give you full control over the invocation. You can create shortcut files interactively, via File Explorer, as described in this answer, or programmatically, as shown in this answer. Similarly, you may create a companion batch file (.cmd or .bat) that invokes your script, because batch file are executed when double-clicked; e.g., if you place a batch file with the same base name as your .ps1 script in the same directory (e.g., foo.cmd next to foo.ps1), you can call it from your batch file as follows; -NoExit keeps the session open:
#powershell.exe -NoExit -File "%~dpn0.ps1" %*
The methods below also enable direct execution of a .ps1 script from a cmd.exe console window, synchronously, inside the same window. In other words: You can execute, say, script foo.ps1 directly as such, instead of having to use the PowerShell CLI, say, powershell.exe -File foo.ps1
[Not recommended] GUI method:
Use File Explorer to make PowerShell execute .ps1 files by default:
Right-click on a .ps1 file and select Properties.
Click on Change... next to the Opens with: label.
Click on More apps on the bottom of the list and scroll down to Look for another app on this PC
Browse to or paste file path C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe and submit.
This method gives you no control over the specifics of the PowerShell invocation and has major limitations; in effect you'll end up with the following behavior:
Major limitations:
Script paths with embedded spaces and ' chars. cannot be invoked this way, because, even though such paths are passed with double quotes, the latter are in effect stripped by PowerShell, because the path is passed to the implied -Command parameter, which first strips (unescaped) double quotes from the command line before interpreting the result as PowerShell code - in which case paths with spaces are seen as multiple arguments / paths that contain (an odd number of) ' cause a syntax error.
Note that if you were to select pwsh.exe instead, the CLI of the cross-platform, install-on-demand PowerShell (Core) 7+ edition, that problem would not arise, because it defaults to the -File parameter - in which case a double-quoted script-file path is properly recognized.
For the difference between PowerShell CLI calls using -Command vs. those using -File, see this answer.
Passing arguments is not supported, which matters if you want to invoke .ps1 files directly from cmd.exe and need to pass arguments.
The redefinition is only in effect for the current user - which is probably a good thing, as other users may not expect this change, which can result in unwanted execution of scripts.
Whatever execution policy is in effect will be honored; e.g., if Restricted is in effect, invocation will fail altogether.
As with the default Run in PowerShell command, the window in which the script runs will automatically close when the script ends - thus, unless the script explicitly prompts the user before exiting, you may not be able to examine its output.
To exercise more control over how PowerShell invokes the script including support for paths with spaces and for passing arguments, use the programmatic method shown in the next section.
Programmatic method:
Important:
The GUI method overrides a programmatic solution, so it must be removed - the code below does this automatically.
Unfortunately, there's another, accidental override that can happen if you have Visual Studio Code installed: Whenever you use File Explorer's shortcut menu to open a file in Visual Studio Code, it unexpectedly becomes the default action. The code below detects this condition and fixes the problem, but it will resurface the next time a .ps1 file is opened this way.
Modify the registry to redefine the Open shortcut-menu command for *.ps1 files at HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\shell\Open\Command, as shown below.
You can run the code as-is to create a user-level file-type definition that:
uses the executable that runs the current PowerShell session, i.e. powershell.exe in Windows PowerShell, and pwsh.exe in PowerShell (Core) 7+.
respects the effective execution policy - add an -ExecutionPolicy argument to override.
loads the profiles first - add -NoProfile to suppress loading; this is primarily of interest if you're planning to directly invoke .ps1 files from cmd.exe, not (just) from File Explorer, in combination with not using -NoExit.
runs in the script in its own directory
keeps the session open after the script exits - remove -NoExit to exit the session when the script ends; this is primarily of interest if you're planning to directly invoke .ps1 files from cmd.exe, not (just) from File Explorer.
If you requirements differ - if you need different CLI parameters and /or you want to use pwsh.exe, i.e. PowerShell (Core) 7+ instead - tweak the code first, by modifying the $cmd = ... line below; see the comments above it.
# Specify if the change should apply to the CURRENT USER only, or to ALL users.
# NOTE: If you set this to $true - which is NOT ADVISABLE -
# you'll need to run this code ELEVATED (as administrator)
$forAllUsers = $false
# Determine the chosen scope's target registry key path.
$targetKey = "$(('HKCU', 'HKLM')[$forAllUsers]):\Software\Classes\Microsoft.PowerShellScript.1\shell\Open\Command"
# In the user-specific hive (HKCU: == HKEY_CURRENT_USER), the target key
# doesn't exist by default (whereas it does in the local-machine hive (HLKM: == HKEY_LOCAL_MACHINE)),
# so we need to make sure that it exists.
if (-not $forAllUsers -and -not (Test-Path -LiteralPath $targetKey)) {
$null = New-Item -Path $targetKey -Force -ErrorAction Stop
}
# Specify the command to use when opening / double-clicking *.ps1 scripts:
# As written here:
# * The script runs in the directory in which it resides.
# * The profiles are loaded (add -NoProfile to change).
# * The current execution policy is respected (add -ExecutionPolicy <policy> to override, if possible)
# * The window stays open after the script exits (remove -NoExit to change)
# For help with all parameters, see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_exe
$cmd = "`"$((Get-Process -Id $PID).Path)`" -nologo -noexit -file `"%1`" %*"
# Write the command to the registry.
Set-ItemProperty -ErrorAction Stop -LiteralPath $targetKey -Name '(default)' -Value $cmd
Write-Verbose -Verbose "$(('User-level', 'Machine-level')[$forAllUsers]) file-type definition for *.ps1 files successfully updated."
# Additionally, make sure that NO OVERRIDES preempt the new definition.
# See if a user override established interactively via File Explorer happens to be defined,
# and remove it, if so.
if ($fileExplorerOverrideKey = Get-Item -ErrorAction Ignore -LiteralPath 'registry::HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\UserChoice') {
Write-Verbose -Verbose 'Removing File Explorer override...'
# Get the parent key path and the key name
$parentKeyPath = $fileExplorerOverrideKey.PSParentPath -replace '^.+?::\w+\\' # Remove the 'Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\' prefix
$keyName = $fileExplorerOverrideKey.PSChildName
$key = $null
try {
# Open the *parent* key for writing.
$key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubkey($parentKeyPath, $true)
# Delete the subkey.
# !! Due to the specific permissions assigned by File Explorer to the key
# !! (an additional DENY access-control entry for the current user, for the key itself only, for the 'Set Value' permission),
# !! using the .DeleteSubKey*Tree*() method fails (Remove-Item implicitly uses this method and therefore fails too)
# !! However, since there should be no nested subkeys, using .DeleteSubkey() should work fine.
$key.DeleteSubKey($keyName)
}
catch {
throw
}
finally {
if ($key) { $key.Close()}
}
}
# See if *Visual Studio Code* was most recently used to open a *.ps1 file:
# If so, it inexplicably OVERRIDES a file-type definition.
# (This doesn't seem to happen with other executables.)
# !! We fix the problem, but it will RESURFACE the next time File Explorer's shortcut menu
# !! is used to open a *.ps1 file in Visual Studio Code.
if ($itm = Get-Item -ErrorAction Ignore -LiteralPath 'registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\OpenWithList') {
if (($names = $itm.GetValueNames().Where({ $itm.GetValue($_) -ceq 'Code.exe' })) -and ($mruList = $itm.GetValue('MRUList')) -and $mruList[0] -in $names) {
Write-Warning "Visual Studio Code was most recently used to open a .ps1 file, which unexpectedly overrides the file-type definition.`nCorrecting the problem for now, but it will resurface the next time you use File Explorer's shortcut menu to open a .ps1 file in Visual Studio Code."
# Note: Normally there aren't, but there *can* be *multiple* Code.exe entries, namely after manual removal of the MRUList:
# The next time you choose to open in VSCode via File Explorer's shortcut menu, an *additional* Code.exe entry is added.
do { # Trim the start of the MRUList until its first entry no longer references Code.exe
$mruList = $mruList.Substring(1)
} while ($mruList[0] -in $names)
# Update the MRUList value in the registry.
$itm | Set-ItemProperty -Name 'MRUList' -Value $mruList
}
}
Explanation of the predefined Run in PowerShell shortcut-menu command:
It is defined in registry key HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\shell\0\Command (as of Windows 10) as follows:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & '%1'"
This command is flawed in that it breaks with script-file paths that happen to contain ' characters.
Unless execution policy AllSigned is in effect - in which case only signed scripts can be executed but are executed without prompting - the command attempts to set the execution policy for the invoked process to Bypass, which means that any script can be executed, but only after the user responds to a confirmation prompt beforehand (irrespective of whether the script is signed or not, and whether it was downloaded from the web or not).
At least in earlier Windows 7 releases / PowerShell versions, the command was misdefined[1] in a way that effectively ignored the attempt to set the process' execution policy, which meant that whatever execution policy was persistently configured applied - and no confirmation prompt was shown.
Unless the targeted script explicitly pauses to wait for user input before exiting, the window in which the script will close automatically when the script finishes, so you may not get to see its output.
The targeted script executes in the directory in which it is located as the working directory (current location)
[1] The earlier, broken command definition was "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-file" "%1" "-Command" "if((Get-ExecutionPolicy ) -ne AllSigned) { Set-ExecutionPolicy -Scope Process Bypass }", which meant what anything after -file "%1" was passed as arguments to file "%1" instead of the intended execution of the commands following -Command; additionally - a moot point - the AllSigned operand would have need to be quoted.
To execute a PS1 file by double-click (to run)
Make a shortcut for the file and set the target to this:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe "C:\Temp\MyPowershellScript.ps1"
Replace the second directory (the one in quotes) with the location of your script.
To read a PS1 file by double-click (to edit)
Same as above, but target ISE instead, as that will force it into edit mode.
C:\Windows\System32\WindowsPowerShell\v1.0\powershell_ise.exe "C:\Temp\MyPowershellScript.ps1"
Server 2012 and newer by default do not associate the .PS1 file extension with the PowerShell executable; rather, they default to open .PS1 files with notepad by default for security reasons.
If you have access, you need to change the file association through the 'default programs' in your control panel for the .PS1 files to execute by double clicking.
Also be aware that you may have to change your execution policy to get particular scripts to run.
Also, as it sounds like this script might be a core automation, you can execute scripts from in another one with either of these, without the need to change the active working directory:
Invoke-Item ""
& ''
I have fixed the registry values so that the .ps1 scripts are executed with double click or with "Run with PowerShell" from any position without problem, even with paths with multiple consecutive spaces and with apostrophes:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.ps1]
#="Microsoft.PowerShellScript.1"
[HKEY_CLASSES_ROOT\Directory\Background\shell\Powershell\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit -Command \"Set-Location -LiteralPath \\\"%V\\\"\""
[HKEY_CLASSES_ROOT\Directory\Shell\Powershell\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit -Command \"Set-Location -LiteralPath \\\"%V\\\"\""
[HKEY_CLASSES_ROOT\Drive\shell\Powershell\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit -Command \"Set-Location -LiteralPath \\\"%V\\\"\""
[HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\DefaultIcon]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\",0"
[HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell\Open\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -File \"%1\""
[HKEY_CLASSES_ROOT\SystemFileAssociations\.ps1\Shell\0\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -File \"%1\""
[HKEY_CLASSES_ROOT\SystemFileAssociations\.ps1\Shell\Edit\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell_ise.exe\" -File \"%1\""
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell]
"ExecutionPolicy"="RemoteSigned"

Powershell script executes correctly when I choose "run with powershell", but not when I "open with" powershell or double-click it [duplicate]

I have my_project.ps1 file from which I am activating virtual environment & starting my project.
currently I need to open my powershell then after I need to go to directory where I have saved my .ps1 file & have to open it from powershell only.
Is there any way so that I can double click on .ps1 file & it will open automatically in power shell ?
By design, double-clicking (opening) *.ps1 files from the Windows [GUI] shell (in this case: Desktop, File Explorer, and the taskbar, via pinned items) does not execute them - instead they're opened for editing in Notepad or in the PowerShell ISE, depending on the Windows / PowerShell version.
However, since at least Windows 7, the shortcut menu for *.ps1 files contains a Run with PowerShell command, which does invoke the script at hand; this may be enough for your purposes, but this invocation method has limitations - see the bottom section for details.
If you do want to redefine double-clicking / opening so that it executes *.ps1 scripts, you have two options:
Note:
For a given script (as opposed to all .ps1 files), you may alternatively create a shortcut file or batch file that launches it, but that isn't a general solution, as you'd have to create a companion file for each and every .ps1 file you want to run by double-clicking. It does, however, give you full control over the invocation. You can create shortcut files interactively, via File Explorer, as described in this answer, or programmatically, as shown in this answer. Similarly, you may create a companion batch file (.cmd or .bat) that invokes your script, because batch file are executed when double-clicked; e.g., if you place a batch file with the same base name as your .ps1 script in the same directory (e.g., foo.cmd next to foo.ps1), you can call it from your batch file as follows; -NoExit keeps the session open:
#powershell.exe -NoExit -File "%~dpn0.ps1" %*
The methods below also enable direct execution of a .ps1 script from a cmd.exe console window, synchronously, inside the same window. In other words: You can execute, say, script foo.ps1 directly as such, instead of having to use the PowerShell CLI, say, powershell.exe -File foo.ps1
[Not recommended] GUI method:
Use File Explorer to make PowerShell execute .ps1 files by default:
Right-click on a .ps1 file and select Properties.
Click on Change... next to the Opens with: label.
Click on More apps on the bottom of the list and scroll down to Look for another app on this PC
Browse to or paste file path C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe and submit.
This method gives you no control over the specifics of the PowerShell invocation and has major limitations; in effect you'll end up with the following behavior:
Major limitations:
Script paths with embedded spaces and ' chars. cannot be invoked this way, because, even though such paths are passed with double quotes, the latter are in effect stripped by PowerShell, because the path is passed to the implied -Command parameter, which first strips (unescaped) double quotes from the command line before interpreting the result as PowerShell code - in which case paths with spaces are seen as multiple arguments / paths that contain (an odd number of) ' cause a syntax error.
Note that if you were to select pwsh.exe instead, the CLI of the cross-platform, install-on-demand PowerShell (Core) 7+ edition, that problem would not arise, because it defaults to the -File parameter - in which case a double-quoted script-file path is properly recognized.
For the difference between PowerShell CLI calls using -Command vs. those using -File, see this answer.
Passing arguments is not supported, which matters if you want to invoke .ps1 files directly from cmd.exe and need to pass arguments.
The redefinition is only in effect for the current user - which is probably a good thing, as other users may not expect this change, which can result in unwanted execution of scripts.
Whatever execution policy is in effect will be honored; e.g., if Restricted is in effect, invocation will fail altogether.
As with the default Run in PowerShell command, the window in which the script runs will automatically close when the script ends - thus, unless the script explicitly prompts the user before exiting, you may not be able to examine its output.
To exercise more control over how PowerShell invokes the script including support for paths with spaces and for passing arguments, use the programmatic method shown in the next section.
Programmatic method:
Important:
The GUI method overrides a programmatic solution, so it must be removed - the code below does this automatically.
Unfortunately, there's another, accidental override that can happen if you have Visual Studio Code installed: Whenever you use File Explorer's shortcut menu to open a file in Visual Studio Code, it unexpectedly becomes the default action. The code below detects this condition and fixes the problem, but it will resurface the next time a .ps1 file is opened this way.
Modify the registry to redefine the Open shortcut-menu command for *.ps1 files at HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\shell\Open\Command, as shown below.
You can run the code as-is to create a user-level file-type definition that:
uses the executable that runs the current PowerShell session, i.e. powershell.exe in Windows PowerShell, and pwsh.exe in PowerShell (Core) 7+.
respects the effective execution policy - add an -ExecutionPolicy argument to override.
loads the profiles first - add -NoProfile to suppress loading; this is primarily of interest if you're planning to directly invoke .ps1 files from cmd.exe, not (just) from File Explorer, in combination with not using -NoExit.
runs in the script in its own directory
keeps the session open after the script exits - remove -NoExit to exit the session when the script ends; this is primarily of interest if you're planning to directly invoke .ps1 files from cmd.exe, not (just) from File Explorer.
If you requirements differ - if you need different CLI parameters and /or you want to use pwsh.exe, i.e. PowerShell (Core) 7+ instead - tweak the code first, by modifying the $cmd = ... line below; see the comments above it.
# Specify if the change should apply to the CURRENT USER only, or to ALL users.
# NOTE: If you set this to $true - which is NOT ADVISABLE -
# you'll need to run this code ELEVATED (as administrator)
$forAllUsers = $false
# Determine the chosen scope's target registry key path.
$targetKey = "$(('HKCU', 'HKLM')[$forAllUsers]):\Software\Classes\Microsoft.PowerShellScript.1\shell\Open\Command"
# In the user-specific hive (HKCU: == HKEY_CURRENT_USER), the target key
# doesn't exist by default (whereas it does in the local-machine hive (HLKM: == HKEY_LOCAL_MACHINE)),
# so we need to make sure that it exists.
if (-not $forAllUsers -and -not (Test-Path -LiteralPath $targetKey)) {
$null = New-Item -Path $targetKey -Force -ErrorAction Stop
}
# Specify the command to use when opening / double-clicking *.ps1 scripts:
# As written here:
# * The script runs in the directory in which it resides.
# * The profiles are loaded (add -NoProfile to change).
# * The current execution policy is respected (add -ExecutionPolicy <policy> to override, if possible)
# * The window stays open after the script exits (remove -NoExit to change)
# For help with all parameters, see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_exe
$cmd = "`"$((Get-Process -Id $PID).Path)`" -nologo -noexit -file `"%1`" %*"
# Write the command to the registry.
Set-ItemProperty -ErrorAction Stop -LiteralPath $targetKey -Name '(default)' -Value $cmd
Write-Verbose -Verbose "$(('User-level', 'Machine-level')[$forAllUsers]) file-type definition for *.ps1 files successfully updated."
# Additionally, make sure that NO OVERRIDES preempt the new definition.
# See if a user override established interactively via File Explorer happens to be defined,
# and remove it, if so.
if ($fileExplorerOverrideKey = Get-Item -ErrorAction Ignore -LiteralPath 'registry::HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\UserChoice') {
Write-Verbose -Verbose 'Removing File Explorer override...'
# Get the parent key path and the key name
$parentKeyPath = $fileExplorerOverrideKey.PSParentPath -replace '^.+?::\w+\\' # Remove the 'Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\' prefix
$keyName = $fileExplorerOverrideKey.PSChildName
$key = $null
try {
# Open the *parent* key for writing.
$key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubkey($parentKeyPath, $true)
# Delete the subkey.
# !! Due to the specific permissions assigned by File Explorer to the key
# !! (an additional DENY access-control entry for the current user, for the key itself only, for the 'Set Value' permission),
# !! using the .DeleteSubKey*Tree*() method fails (Remove-Item implicitly uses this method and therefore fails too)
# !! However, since there should be no nested subkeys, using .DeleteSubkey() should work fine.
$key.DeleteSubKey($keyName)
}
catch {
throw
}
finally {
if ($key) { $key.Close()}
}
}
# See if *Visual Studio Code* was most recently used to open a *.ps1 file:
# If so, it inexplicably OVERRIDES a file-type definition.
# (This doesn't seem to happen with other executables.)
# !! We fix the problem, but it will RESURFACE the next time File Explorer's shortcut menu
# !! is used to open a *.ps1 file in Visual Studio Code.
if ($itm = Get-Item -ErrorAction Ignore -LiteralPath 'registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\OpenWithList') {
if (($names = $itm.GetValueNames().Where({ $itm.GetValue($_) -ceq 'Code.exe' })) -and ($mruList = $itm.GetValue('MRUList')) -and $mruList[0] -in $names) {
Write-Warning "Visual Studio Code was most recently used to open a .ps1 file, which unexpectedly overrides the file-type definition.`nCorrecting the problem for now, but it will resurface the next time you use File Explorer's shortcut menu to open a .ps1 file in Visual Studio Code."
# Note: Normally there aren't, but there *can* be *multiple* Code.exe entries, namely after manual removal of the MRUList:
# The next time you choose to open in VSCode via File Explorer's shortcut menu, an *additional* Code.exe entry is added.
do { # Trim the start of the MRUList until its first entry no longer references Code.exe
$mruList = $mruList.Substring(1)
} while ($mruList[0] -in $names)
# Update the MRUList value in the registry.
$itm | Set-ItemProperty -Name 'MRUList' -Value $mruList
}
}
Explanation of the predefined Run in PowerShell shortcut-menu command:
It is defined in registry key HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\shell\0\Command (as of Windows 10) as follows:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & '%1'"
This command is flawed in that it breaks with script-file paths that happen to contain ' characters.
Unless execution policy AllSigned is in effect - in which case only signed scripts can be executed but are executed without prompting - the command attempts to set the execution policy for the invoked process to Bypass, which means that any script can be executed, but only after the user responds to a confirmation prompt beforehand (irrespective of whether the script is signed or not, and whether it was downloaded from the web or not).
At least in earlier Windows 7 releases / PowerShell versions, the command was misdefined[1] in a way that effectively ignored the attempt to set the process' execution policy, which meant that whatever execution policy was persistently configured applied - and no confirmation prompt was shown.
Unless the targeted script explicitly pauses to wait for user input before exiting, the window in which the script will close automatically when the script finishes, so you may not get to see its output.
The targeted script executes in the directory in which it is located as the working directory (current location)
[1] The earlier, broken command definition was "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-file" "%1" "-Command" "if((Get-ExecutionPolicy ) -ne AllSigned) { Set-ExecutionPolicy -Scope Process Bypass }", which meant what anything after -file "%1" was passed as arguments to file "%1" instead of the intended execution of the commands following -Command; additionally - a moot point - the AllSigned operand would have need to be quoted.
To execute a PS1 file by double-click (to run)
Make a shortcut for the file and set the target to this:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe "C:\Temp\MyPowershellScript.ps1"
Replace the second directory (the one in quotes) with the location of your script.
To read a PS1 file by double-click (to edit)
Same as above, but target ISE instead, as that will force it into edit mode.
C:\Windows\System32\WindowsPowerShell\v1.0\powershell_ise.exe "C:\Temp\MyPowershellScript.ps1"
Server 2012 and newer by default do not associate the .PS1 file extension with the PowerShell executable; rather, they default to open .PS1 files with notepad by default for security reasons.
If you have access, you need to change the file association through the 'default programs' in your control panel for the .PS1 files to execute by double clicking.
Also be aware that you may have to change your execution policy to get particular scripts to run.
Also, as it sounds like this script might be a core automation, you can execute scripts from in another one with either of these, without the need to change the active working directory:
Invoke-Item ""
& ''
I have fixed the registry values so that the .ps1 scripts are executed with double click or with "Run with PowerShell" from any position without problem, even with paths with multiple consecutive spaces and with apostrophes:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.ps1]
#="Microsoft.PowerShellScript.1"
[HKEY_CLASSES_ROOT\Directory\Background\shell\Powershell\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit -Command \"Set-Location -LiteralPath \\\"%V\\\"\""
[HKEY_CLASSES_ROOT\Directory\Shell\Powershell\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit -Command \"Set-Location -LiteralPath \\\"%V\\\"\""
[HKEY_CLASSES_ROOT\Drive\shell\Powershell\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit -Command \"Set-Location -LiteralPath \\\"%V\\\"\""
[HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\DefaultIcon]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\",0"
[HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell\Open\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -File \"%1\""
[HKEY_CLASSES_ROOT\SystemFileAssociations\.ps1\Shell\0\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -File \"%1\""
[HKEY_CLASSES_ROOT\SystemFileAssociations\.ps1\Shell\Edit\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell_ise.exe\" -File \"%1\""
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell]
"ExecutionPolicy"="RemoteSigned"

Any way to double-click on .PS1 file & open them in PowerShell?

I have my_project.ps1 file from which I am activating virtual environment & starting my project.
currently I need to open my powershell then after I need to go to directory where I have saved my .ps1 file & have to open it from powershell only.
Is there any way so that I can double click on .ps1 file & it will open automatically in power shell ?
By design, double-clicking (opening) *.ps1 files from the Windows [GUI] shell (in this case: Desktop, File Explorer, and the taskbar, via pinned items) does not execute them - instead they're opened for editing in Notepad or in the PowerShell ISE, depending on the Windows / PowerShell version.
However, since at least Windows 7, the shortcut menu for *.ps1 files contains a Run with PowerShell command, which does invoke the script at hand; this may be enough for your purposes, but this invocation method has limitations - see the bottom section for details.
If you do want to redefine double-clicking / opening so that it executes *.ps1 scripts, you have two options:
Note:
For a given script (as opposed to all .ps1 files), you may alternatively create a shortcut file or batch file that launches it, but that isn't a general solution, as you'd have to create a companion file for each and every .ps1 file you want to run by double-clicking. It does, however, give you full control over the invocation. You can create shortcut files interactively, via File Explorer, as described in this answer, or programmatically, as shown in this answer. Similarly, you may create a companion batch file (.cmd or .bat) that invokes your script, because batch file are executed when double-clicked; e.g., if you place a batch file with the same base name as your .ps1 script in the same directory (e.g., foo.cmd next to foo.ps1), you can call it from your batch file as follows; -NoExit keeps the session open:
#powershell.exe -NoExit -File "%~dpn0.ps1" %*
The methods below also enable direct execution of a .ps1 script from a cmd.exe console window, synchronously, inside the same window. In other words: You can execute, say, script foo.ps1 directly as such, instead of having to use the PowerShell CLI, say, powershell.exe -File foo.ps1
[Not recommended] GUI method:
Use File Explorer to make PowerShell execute .ps1 files by default:
Right-click on a .ps1 file and select Properties.
Click on Change... next to the Opens with: label.
Click on More apps on the bottom of the list and scroll down to Look for another app on this PC
Browse to or paste file path C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe and submit.
This method gives you no control over the specifics of the PowerShell invocation and has major limitations; in effect you'll end up with the following behavior:
Major limitations:
Script paths with embedded spaces and ' chars. cannot be invoked this way, because, even though such paths are passed with double quotes, the latter are in effect stripped by PowerShell, because the path is passed to the implied -Command parameter, which first strips (unescaped) double quotes from the command line before interpreting the result as PowerShell code - in which case paths with spaces are seen as multiple arguments / paths that contain (an odd number of) ' cause a syntax error.
Note that if you were to select pwsh.exe instead, the CLI of the cross-platform, install-on-demand PowerShell (Core) 7+ edition, that problem would not arise, because it defaults to the -File parameter - in which case a double-quoted script-file path is properly recognized.
For the difference between PowerShell CLI calls using -Command vs. those using -File, see this answer.
Passing arguments is not supported, which matters if you want to invoke .ps1 files directly from cmd.exe and need to pass arguments.
The redefinition is only in effect for the current user - which is probably a good thing, as other users may not expect this change, which can result in unwanted execution of scripts.
Whatever execution policy is in effect will be honored; e.g., if Restricted is in effect, invocation will fail altogether.
As with the default Run in PowerShell command, the window in which the script runs will automatically close when the script ends - thus, unless the script explicitly prompts the user before exiting, you may not be able to examine its output.
To exercise more control over how PowerShell invokes the script including support for paths with spaces and for passing arguments, use the programmatic method shown in the next section.
Programmatic method:
Important:
The GUI method overrides a programmatic solution, so it must be removed - the code below does this automatically.
Unfortunately, there's another, accidental override that can happen if you have Visual Studio Code installed: Whenever you use File Explorer's shortcut menu to open a file in Visual Studio Code, it unexpectedly becomes the default action. The code below detects this condition and fixes the problem, but it will resurface the next time a .ps1 file is opened this way.
Modify the registry to redefine the Open shortcut-menu command for *.ps1 files at HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\shell\Open\Command, as shown below.
You can run the code as-is to create a user-level file-type definition that:
uses the executable that runs the current PowerShell session, i.e. powershell.exe in Windows PowerShell, and pwsh.exe in PowerShell (Core) 7+.
respects the effective execution policy - add an -ExecutionPolicy argument to override.
loads the profiles first - add -NoProfile to suppress loading; this is primarily of interest if you're planning to directly invoke .ps1 files from cmd.exe, not (just) from File Explorer, in combination with not using -NoExit.
runs in the script in its own directory
keeps the session open after the script exits - remove -NoExit to exit the session when the script ends; this is primarily of interest if you're planning to directly invoke .ps1 files from cmd.exe, not (just) from File Explorer.
If you requirements differ - if you need different CLI parameters and /or you want to use pwsh.exe, i.e. PowerShell (Core) 7+ instead - tweak the code first, by modifying the $cmd = ... line below; see the comments above it.
# Specify if the change should apply to the CURRENT USER only, or to ALL users.
# NOTE: If you set this to $true - which is NOT ADVISABLE -
# you'll need to run this code ELEVATED (as administrator)
$forAllUsers = $false
# Determine the chosen scope's target registry key path.
$targetKey = "$(('HKCU', 'HKLM')[$forAllUsers]):\Software\Classes\Microsoft.PowerShellScript.1\shell\Open\Command"
# In the user-specific hive (HKCU: == HKEY_CURRENT_USER), the target key
# doesn't exist by default (whereas it does in the local-machine hive (HLKM: == HKEY_LOCAL_MACHINE)),
# so we need to make sure that it exists.
if (-not $forAllUsers -and -not (Test-Path -LiteralPath $targetKey)) {
$null = New-Item -Path $targetKey -Force -ErrorAction Stop
}
# Specify the command to use when opening / double-clicking *.ps1 scripts:
# As written here:
# * The script runs in the directory in which it resides.
# * The profiles are loaded (add -NoProfile to change).
# * The current execution policy is respected (add -ExecutionPolicy <policy> to override, if possible)
# * The window stays open after the script exits (remove -NoExit to change)
# For help with all parameters, see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_exe
$cmd = "`"$((Get-Process -Id $PID).Path)`" -nologo -noexit -file `"%1`" %*"
# Write the command to the registry.
Set-ItemProperty -ErrorAction Stop -LiteralPath $targetKey -Name '(default)' -Value $cmd
Write-Verbose -Verbose "$(('User-level', 'Machine-level')[$forAllUsers]) file-type definition for *.ps1 files successfully updated."
# Additionally, make sure that NO OVERRIDES preempt the new definition.
# See if a user override established interactively via File Explorer happens to be defined,
# and remove it, if so.
if ($fileExplorerOverrideKey = Get-Item -ErrorAction Ignore -LiteralPath 'registry::HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\UserChoice') {
Write-Verbose -Verbose 'Removing File Explorer override...'
# Get the parent key path and the key name
$parentKeyPath = $fileExplorerOverrideKey.PSParentPath -replace '^.+?::\w+\\' # Remove the 'Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\' prefix
$keyName = $fileExplorerOverrideKey.PSChildName
$key = $null
try {
# Open the *parent* key for writing.
$key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubkey($parentKeyPath, $true)
# Delete the subkey.
# !! Due to the specific permissions assigned by File Explorer to the key
# !! (an additional DENY access-control entry for the current user, for the key itself only, for the 'Set Value' permission),
# !! using the .DeleteSubKey*Tree*() method fails (Remove-Item implicitly uses this method and therefore fails too)
# !! However, since there should be no nested subkeys, using .DeleteSubkey() should work fine.
$key.DeleteSubKey($keyName)
}
catch {
throw
}
finally {
if ($key) { $key.Close()}
}
}
# See if *Visual Studio Code* was most recently used to open a *.ps1 file:
# If so, it inexplicably OVERRIDES a file-type definition.
# (This doesn't seem to happen with other executables.)
# !! We fix the problem, but it will RESURFACE the next time File Explorer's shortcut menu
# !! is used to open a *.ps1 file in Visual Studio Code.
if ($itm = Get-Item -ErrorAction Ignore -LiteralPath 'registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ps1\OpenWithList') {
if (($names = $itm.GetValueNames().Where({ $itm.GetValue($_) -ceq 'Code.exe' })) -and ($mruList = $itm.GetValue('MRUList')) -and $mruList[0] -in $names) {
Write-Warning "Visual Studio Code was most recently used to open a .ps1 file, which unexpectedly overrides the file-type definition.`nCorrecting the problem for now, but it will resurface the next time you use File Explorer's shortcut menu to open a .ps1 file in Visual Studio Code."
# Note: Normally there aren't, but there *can* be *multiple* Code.exe entries, namely after manual removal of the MRUList:
# The next time you choose to open in VSCode via File Explorer's shortcut menu, an *additional* Code.exe entry is added.
do { # Trim the start of the MRUList until its first entry no longer references Code.exe
$mruList = $mruList.Substring(1)
} while ($mruList[0] -in $names)
# Update the MRUList value in the registry.
$itm | Set-ItemProperty -Name 'MRUList' -Value $mruList
}
}
Explanation of the predefined Run in PowerShell shortcut-menu command:
It is defined in registry key HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\shell\0\Command (as of Windows 10) as follows:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & '%1'"
This command is flawed in that it breaks with script-file paths that happen to contain ' characters.
Unless execution policy AllSigned is in effect - in which case only signed scripts can be executed but are executed without prompting - the command attempts to set the execution policy for the invoked process to Bypass, which means that any script can be executed, but only after the user responds to a confirmation prompt beforehand (irrespective of whether the script is signed or not, and whether it was downloaded from the web or not).
At least in earlier Windows 7 releases / PowerShell versions, the command was misdefined[1] in a way that effectively ignored the attempt to set the process' execution policy, which meant that whatever execution policy was persistently configured applied - and no confirmation prompt was shown.
Unless the targeted script explicitly pauses to wait for user input before exiting, the window in which the script will close automatically when the script finishes, so you may not get to see its output.
The targeted script executes in the directory in which it is located as the working directory (current location)
[1] The earlier, broken command definition was "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-file" "%1" "-Command" "if((Get-ExecutionPolicy ) -ne AllSigned) { Set-ExecutionPolicy -Scope Process Bypass }", which meant what anything after -file "%1" was passed as arguments to file "%1" instead of the intended execution of the commands following -Command; additionally - a moot point - the AllSigned operand would have need to be quoted.
To execute a PS1 file by double-click (to run)
Make a shortcut for the file and set the target to this:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe "C:\Temp\MyPowershellScript.ps1"
Replace the second directory (the one in quotes) with the location of your script.
To read a PS1 file by double-click (to edit)
Same as above, but target ISE instead, as that will force it into edit mode.
C:\Windows\System32\WindowsPowerShell\v1.0\powershell_ise.exe "C:\Temp\MyPowershellScript.ps1"
Server 2012 and newer by default do not associate the .PS1 file extension with the PowerShell executable; rather, they default to open .PS1 files with notepad by default for security reasons.
If you have access, you need to change the file association through the 'default programs' in your control panel for the .PS1 files to execute by double clicking.
Also be aware that you may have to change your execution policy to get particular scripts to run.
Also, as it sounds like this script might be a core automation, you can execute scripts from in another one with either of these, without the need to change the active working directory:
Invoke-Item ""
& ''
I have fixed the registry values so that the .ps1 scripts are executed with double click or with "Run with PowerShell" from any position without problem, even with paths with multiple consecutive spaces and with apostrophes:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.ps1]
#="Microsoft.PowerShellScript.1"
[HKEY_CLASSES_ROOT\Directory\Background\shell\Powershell\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit -Command \"Set-Location -LiteralPath \\\"%V\\\"\""
[HKEY_CLASSES_ROOT\Directory\Shell\Powershell\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit -Command \"Set-Location -LiteralPath \\\"%V\\\"\""
[HKEY_CLASSES_ROOT\Drive\shell\Powershell\command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit -Command \"Set-Location -LiteralPath \\\"%V\\\"\""
[HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\DefaultIcon]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\",0"
[HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell\Open\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -File \"%1\""
[HKEY_CLASSES_ROOT\SystemFileAssociations\.ps1\Shell\0\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -File \"%1\""
[HKEY_CLASSES_ROOT\SystemFileAssociations\.ps1\Shell\Edit\Command]
#="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell_ise.exe\" -File \"%1\""
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell]
"ExecutionPolicy"="RemoteSigned"

Setting environment variables with batch file lauched by Powershell script

I have a batch script called SET_ENV.bat which contains environment variables that are used by other batch scripts. Currently this SET_ENV.bat is lauched by existing batch scripts.
Now I have a need to use Powershell script and I would like to launch the same SET_ENV.bat. I managed to do this using:
cmd.exe /c ..\..\SET_ENV.bat
I know that the batch file was run because it contained an echo
echo *** Set the environment variables for the processes ***
But after looking at the environment variables, I can see that none of them have been updated. Is there something that is preventing me from updating environment variables with Powershell + batch file combo?
I have tried SET_ENV.bat directly from command line and it works. I have also tried Start-Process cmdlet with "-Verb runAs" but that didn't do any good.
Launching PowerShell again at the end of the batch commands will keep every environment variable so far.
My use case was: set up Anaconda environment, set up MSVC environment, continue with that. Problem is both Anaconda and MSCV have a separate batch script that initialises the env.
The following command starting from PowerShell will:
initialise Anaconda
initialise MSVC
re-launch PowerShell
cmd.exe "/K" '%USERPROFILE%\apps\anaconda3\Scripts\activate.bat %USERPROFILE%\apps\anaconda3 && "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" && powershell'
Just swap the paths with what you need. Note that if the path contains spaces in needs to be inside double quotes ".
Breaking down the call above:
cmd.exe "/K": call cmd and do not exit after the commands finish executing /K
The rest is the full command, it is wrapped in single quotes '.
%USERPROFILE%\apps\anaconda3\Scripts\activate.bat %USERPROFILE%\apps\anaconda3: calls activate.bat with parameter ...\anaconda3
&& "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat": && and if the previous command didn't fail, run the MSVC vars setup file. This is wrapped in " as it has spaces in it.
&& powershell: finally run PowerShell. This will now contain all environment variables from the ones above.
Just adding a better way of doing the aforementioned setup: using Anaconda's PowerShell init script to actually get it to display the environment name on the prompt. I won't break down this as it's just a modified command above.
cmd.exe "/K" '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" && powershell -noexit -command "& ''~\apps\anaconda3\shell\condabin\conda-hook.ps1'' ; conda activate ''~\apps\anaconda3'' "'
Note that the single quotes in the powershell call are all doubled up to escape them
Environment variables are local to a process and get inherited (by default at least) to new child processes. In your case you launch a new instance of cmd, which inherits your PowerShell's environment variables, but has its own environment. The batch file then changes the environment of that cmd instance, which closes afterwards and you return back to your PowerShell script. Naturally, nothing in PowerShell's environment has changed.
It works in cmd since batch files are executed in the same process, so a batch file can set environment variables and subsequently they are available, since the batch file wasn't executed in a new process. If you use cmd /c setenv.cmd in an interactive cmd session you will find that your environment hasn't changed either.
You can try another option, such as specifying the environment variables in a language-agnostic file, to be read by either cmd or PowerShell to set the environment accordingly. Or you could launch your PowerShell scripts from cmd after first running your batch file. Or you could set those environment variables under your user account to no longer have to care for them. Or you just have one setenv.cmd and one setenv.ps1 and keep them updated in sync.
Summary
Write the environment variables to file and load them after.
Example
I've included an MWE below that exemplifies this by saving and loading the VS-studio environment.
Usage
To run the script, call New-Environment. You will now be in the VS2022 environment.
How it works
The first time New-Environment is called, the VS-studio environment batch file runs, but the results are saved to disk. On returning to PowerShell the results are loaded from disk. Subsequent times just use the saved results without running the environment activator again (because it's slow). The New-Environment -refresh parameter may be used if you do want to resave the VS-studio environment again, for instance if anything has changed.
Script
NOTE: This script must be present in your powershell $profile so the second instance can access the function! Please ensure to change the VS path to reflect your own installation.
function New-Environment()
{
param (
[switch]
$refresh
)
Write-Host "Env vars now: $($(Get-ChildItem env: | measure-object).Count)"
$fileName = "$home\my_vsenviron.json"
if ((-not $refresh) -and (Test-Path $fileName -PathType Leaf))
{
Import-Environment($fileName)
return;
}
$script = '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && '
$script += "pwsh --command Export-Environment `"$fileName`""
&"cmd.exe" "/C" $script
Import-Environment($fileName)
}
function Export-Environment($fileName)
{
Get-ChildItem env: | Select-Object name,value | ConvertTo-Json | Out-File $fileName
Write-Host "I have exported the environment to $fileName"
}
function Import-Environment($fileName)
{
Get-Content $fileName | ConvertFrom-json | ForEach-Object -process {Set-Item "env:$($_.Name)" "$($_.Value)"}
Write-Host "I have imported the environment from $fileName"
Write-Host "Env vars now: $($(Get-ChildItem env: | measure-object).Count)"
}

How to replace the Visual Studio command prompt with Powershell?

I am trying all kinds of variants of the following command:
powershell -command "'C:\Program Files (x86)\Microsoft Visual Studio 10.0\vc\vcvarsall.bat' x86" -noexit
I am hoping to run Powershell with the VS environment vars - to no avail. I just cannot get the quotes right.
Have a look at the PowerShell Community Extensions. It comes with a command to invoke a batch file, "remember" what env vars it set and bring those into your PowerShell session. In fact, my profile does just this with the VS vars e.g.:
Import-Module Pscx
function Import-VS9Vars
{
$vcargs = ?: {$Pscx:Is64BitProcess} {'amd64'} {'x86'}
Push-EnvironmentBlock -Description "Before importing VS 2008 $vcargs var"
Invoke-BatchFile "${env:VS90COMNTOOLS}..\..\VC\vcvarsall.bat" $vcargs
}
function Import-VS10Vars
{
$vcargs = ?: {$Pscx:Is64BitProcess} {'amd64'} {'x86'}
Push-EnvironmentBlock -Description "Before importing VS 2010 $vcargs vars"
Invoke-BatchFile "${env:VS100COMNTOOLS}..\..\VC\vcvarsall.bat" $vcargs
}
Import-VS10Vars
The important command is the Invoke-BatchFile. The source to this function comes in the download so if you don't want to use the whole module, you can copy out just that one function if you'd like.
Check out Get-BatchFile from Bruce Payette's "Windows PowerShell in Action, Second Edition"
Link to Get-BatchFile
It’s also possible to use these batch files from PowerShell by
executing them, dumping the changes that have been made to the
environment, and then importing those changes back into the PowerShell
environment.
There is an example here.
And don't forget the icons :)