How to start another PowerShell session in a separate window while keeping the environment? - windows

While working in PowerShell I tend to quickly switch to admin mode by typing
Start-Process wt -Verb runas
When I do so, a new window appears (sadly, no sudo in Windows). In that new session however, the environment is totally fresh. Is it possible to keep variables, aliases, working dir and all other stuff of similar matter while jumping to a new window? If not, then well, it's a valid answer.
To give some example, I am looking for this behavior:
First window
C:\test> $x = 123
C:\test> Start-Process wt
New window
C:\test> $x
123

By (security-minded) design, elevated sessions (-Verb RunAs) do not inherit the caller's environment variables.
Also, wether or not you use -Verb RunAs, the state of a PowerShell session (aliases, functions, current location, ...) is never inherited when you launch another PowerShell process, such as with Start-Process.
You can work around the issue by explicitly and selectively redefining the state of interest via commands executed in the elevated session, based on values from the caller's state, but that is quite cumbersome and has limitations, as the following example shows:
# Define a few things to copy to the elevated session.
$x1 = 123
$x2 = '3" of snow' # !! See the caveat re regular variables below.
$env:foo = 1
$env:foo2 = 2
Set-Alias bar Get-Date
function baz { "hello, world" }
# Note: The following only copies the definitions above.
# You could try to copy ALL such definitions, by omitting a target name / pattern:
# Get-ChildItem env:
# Get-ChildItem function:
# Get-ChildItem alias:
# CAVEAT: This will NOT generally work with *regular variables*.
Start-Process -Verb RunAs powershell #"
-NoExit -Command Set-Location -LiteralPath \"$((Get-Location -PSProvider FileSystem).ProviderPath)\"
$(Get-Variable x? | ForEach-Object { "`${$($_.Name)} = $(if ($_.Value -is [string]) { "'{0}'" -f ($_.Value -replace "'", "''" -replace '"', '\"') } else { $_.Value }); " })
$(Get-ChildItem env:foo* | ForEach-Object { "Set-Item \`"env:$($_.Name)\`" \`"$($_.Value -replace '"', '\"\"')\`"; " })
$(Get-ChildItem function:bar | ForEach-Object { "`$function:$($_.Name) = \`"$($_.Definition -replace '"', '\"\"')\`"; " })
$(Get-ChildItem alias:baz | ForEach-Object { "`$alias:$($_.Name) = \`"$($_.Definition)\`"; " })
"#
Important:
I've omitted the call to Windows Terminal (wt.exe), as that would create another PowerShell session, which means that only the following definitions would be preserved for that session:
Environment variables.
The current location (working directory), IF its default shell is configured to use the parent process' working directory. Alternatively, and more predictably, pass the working dir. explicitly with the -d option: wt.exe -d \"$((Get-Location -PSProvider FileSystem).ProviderPath)\"
If that is enough, you can remove the commands that preserve aliases, functions, and regular variables, add -WindowStyle Hidden to Start-Process, remove -NoExit before -Command in the argument list, and add a wt.exe call at the bottom.
Preserving the other types of definitions requires working directly in the elevated powershell session, which will invariably use a regular (conhost.exe) console window, however.
In general, it's best to place definitions that should be available in both regular and elevated sessions in your $PROFILE file.
Complementarily, see this answer for convenience function Enter-AdminPSSession, which allows you to pass a script block to execute in the elevated session, to which you can pass values from the caller's state as arguments.
Note:
The above uses the Windows PowerShell CLI, powershell.exe. To use PowerShell (Core) 7+ instead, substitute pwsh.exe.
The above covers preserving the current file-system location, environment variables, aliases, and functions in a generic fashion.
Caveat: By contrast, preserving regular variables is limited to strings and numbers - in essence, instances of those data types whose stringified representation is recognized as such when interpreted as a source-code literal.
With nontrivial additional effort, supporting more data types is possible, by using Base64 encoding with the CLI's -EncodedCommand and -EncodedArguments parameters as shown in this answer, but the range of types that can be represented with type fidelity is fundamentally limited by PowerShell's XML-based serialization infrastructure - see this answer.

You can not keep variables, you will lose them immediately after the new window is created, the best you can do instead is to create a script containing all your activities then save it in the same working directory.
When you open a new window just call your script that will be able to give you the same information as in the other window.

Related

How can i use a cmd variable in a powershell command?

I have created a simple .bat file. In this batch file i have a variable named urlExample which is equal to "example.com".
In the same batch file i want to use this variable urlExample in a powershell command.
Specifically, consider the following code:
#echo off
set urlExample = "example.com"
powershell -ExecutionPolicy Bypass -Command "& {$WebClient = New-Object System.Net.WebClient;$WebClient.DownloadFile($urlExample,"C:\.....somePath")
How could i achieve using the urlExample inside the WebClient command?
P.s. I don't want to simply put the url in the DownloadFile's first argument. I want to pass it with a batch variable.
Thanks in advance
In cmd.exe, all variables are also environment variables, such as %urlExample% in your case, and child processes - such as a call to powershell.exe, the Windows PowerShell CLI, inherit environment variables.
By contrast, PowerShell also has shell(-only) variables (e.g., $urlExample, limited to that session only), whereas environment variables must be accessed via the env namespace (e.g. $env:urlExample - see the conceptual about_Environment_Variables help topic).
While you can use string interpolation on the cmd.exe side to "bake in" the values of cmd.exe-defined environment variables, by embedding %urlExample% in the -Command argument, it is more robust to let PowerShell access the environment variable, by referencing $env:urlExample.
Therefore:
#echo off
:: Note: No spaces around "=", double-quote the name *and* the value.
set "urlExample=example.com"
:: Note the reference to $env:urlExample
:: Embedded " chars. are escaped as \"
powershell -ExecutionPolicy Bypass -Command "$WebClient = New-Object System.Net.WebClient; $WebClient.DownloadFile($env:urlExample, \"C:\.....somePath\")"
Note:
-ExecutionPolicy Bypass isn't strictly needed in this case, given that no execution of a script file is (.ps1) is involved here (whether directly with -File or indirectly, as part of a -Command argument).
However, given that the effective execution policy also applies in less obvious scenarios when you use -Command (the default parameter of powershell.exe)[1] - such as (possibly implicitly) loading a module that is either a script module (*.psm1) and / or contains formatting / type-definition data (*.ps1xml) - using -ExecutionPolicy Bypass is a good habit to form to ensure predictable execution, assuming that you trust the code you're invoking.
As Compo points out, another good habit to form to ensure a predictable execution environment is to use -NoProfile, which bypasses loading of PowerShell's profile files. In addition to preventing potentially unnecessary / unwanted modifications of the execution environment by the profiles, bypassing profile loading also speeds up the command.
[1] Note that pwsh, the PowerShell (Core) CLI, now defaults to -File.

How to escape poison character in this mixed cmd / powershell code?

I asked for a first explanation "here" and "here" but going to try a more complex situation I was unable (after two hours of trying) to understand how to solve. I read how the regular expression works but nothing, I went into the ball.
The modified code is this:
(Fsutil Dirty Query %SystemDrive%>Nul)||(powershell.exe -c "[Environment]::CommandLine; Start -Verb RunAs cmd /k, ("^""%~f0"^"" -replace '[;,()= &^]', '^$&')" & echo exit)
and the folder with the poison characters is this:
C:\Users\fposc\Desktop\Pie & tea % # ' $^
I have tried to escape the ^ in the regular expression with \^ but don't work. I have escaped also ( and ) with \( and \). But nothing work:
(Fsutil Dirty Query %SystemDrive%>Nul)||(powershell.exe -c "[Environment]::CommandLine; Start -Verb RunAs cmd /c, ("^""%~f0"^"" -replace '[;,\(\)= &\^]', '^$&')" & exit)
I added the round brackets because I wanted to put all possible characters to make the code as generic as possible.
I don't know if I was right to open another question. Maybe I should change the original question? Since other combinations are possible and not having understood the mechanism I could open many other similar questions. What do you advise me to do?
The problem is the presence of $ in your folder name, which causes the PowerShell command to interpret it as the start of a variable reference.
The workaround is to use an aux. environment variable to store the batch file's full path and let PowerShell perform its escaping based on this variable's value:
:: Unless already elevated, re-invoke this batch file with elevation,
:: via PowerShell.
set "__THISFILE=%~f0"
Fsutil Dirty Query %SystemDrive% >Nul || (powershell.exe -c "Start-Process -Verb RunAs cmd /k, ($env:__THISFILE -replace '[ &%%^]', '^$&')" & exit)
I have updated the answer to your original question to incorporate this approach, which now shows a - hopefully - robust approach to on-demand re-invocation of a batch file with elevation, including support for arguments.

Open a command file with Windows PowerShell running it directly

I want to make a file having Windows Powershell commands. Then I want to open it with windows powershell directly and without pressing any key I want windows powershell start running those commands directly same as command prompy I can make .cmd or .bat file.
For example:
These are two commands or Powershell, I want to save this file. Then I want directly execute this file by powershell. I have tried to save it as ps1 and ps2 extension as well but not working. Many methods online are not working. Any solution?
PowerShell script files, across all versions, use the .ps1 filename extension.
From within PowerShell, you can invoke them directly, e.g., .\script.ps1
Note that, unlike in cmd.exe, you must use .\ (or a full path) in order to execute a file located in the current directory - just script.ps1 won't work - see this answer for background information.
From cmd.exe, you must use PowerShell's CLI (powershell.exe in Windows PowerShell / pwsh in PowerShell [Core] v6+) in order to execute a script file:
powershell.exe -File script.ps1
pwsh -File script.ps1 (-File may be omitted)
Note that with -File the .\-prefix is not required.
However, if you use -Command (-c) instead (which is the default with powershell.exe, whereas pwsh now defaults to -File), you do need the .\, because the -Command argument(s) are interpreted as a piece of PowerShell code, i.e. as if you had submitted it inside a PowerShell session.
You've discovered this in your own answer, where you pass a PowerShell command directly to the (implied) -Command parameter.
Note, however, that it's better to double-quote such commands, so as to prevent cmd.exe from interpreting certain characters itself, which breaks the call.
For instance, the following call would break, if you didn't enclose the -Command (-c) argument in "...":
# From cmd.exe; "..." required.
C:\>powershell.exe -c "Write-Output 'a & b'"
a & b
Another important consideration is that you need to escape embedded " chars. as \" for the CLI (even though PowerShell-internally you would use `" or ""):
# From cmd.exe; note the inner " escaped as \"
C:\>powershell.exe -c "Write-Output \"hi there\""
hi there
I have found the solution. I use command powershell.exe and can directly execute powershell commands within cmd.
powershell.exe $MyVariable=Get-Content .\Path.txt
is working fine for me

How to escape schtasks /tr arguments

I need to schedule a PowerShell task like following:
powershell.exe -file ..\Execute\execute.ps1
And I have tried assigning it to an argument $Argument then pass it to schtasks like following:
$Argument = "powershell.exe -file '..\Execute\execute.ps1'"
schtasks /create /tn "SOE_Checks" /tr $Argument /sc DAILY /st 05:00 /ru "System" /rl HIGHEST /f
but after running above code, nothing happened - while the task is created successfully, it appears not to run.
I have also tried assigning it to $Argument without the quotes, it worked but I got the following warnings:
ERROR: Invalid syntax. Value expected for '/tr'.
Type "SCHTASKS /CREATE /?" for usage.
Can anyone please let me know what I have done wrong here? (I am aware that I can accomplish this using PowerShell's New-ScheduledTaskAction but I want it to work this way)
Just want to add that if I change the file path to a specific location in $Argument like this, $Argument = "powershell.exe -file 'C:\SOE\Execute\execute.ps1'", it works fine without any warnings but this is not ideal.
I have read this but it does not work for me
Scheduled tasks created with schtasks.exe execute with the working directory set to $env:windir\system32[1], so unless your script happens to be located in ..\Execute\execute.ps1 relative to there, your command won't work as intended.
If you don't want to hard-code the script path directly into the command, construct the command dynamically, by resolving the relative path to an absolute one when you assign to $Argument:
$Argument = 'powershell.exe -file \"{0}\"' -f (Convert-Path ..\Execute\execute.ps1)
Note the - unfortunate - need to escape the embedded " as \", which is longstanding bug that hasn't been fixed for the sake of backward compatibility - see this GitHub docs issue for background.
Convert-Path resolves a relative path to an absolute one.
Note that the relative path must refer to an existing file (or directory).
Similarly, relative paths inside your script will be relative to $env:windir\system32 too; to make them relative to the script's directory, change to your script's directory first by executing Set-Location $PSScriptRoot at the start of your script.
Optional reading: How to quote commands that run from a scheduled task:
Note: Virtually the same rules apply as when running a command from the Windows Run dialog (press WinKey+R), which you can use to test-drive a command (the command to pass to schtasks /tr, without outer quoting, not the whole schtasks command line) - though note that the working directory will be the user's home directory, and that you won't be able to use '...'-quoting around the PowerShell CLI's -File argument - see below):
cmd.exe is NOT involved during execution, which means:
You needn't worry about non-double-quoted use of cmd.exe metacharacters such as &, for instance, so you can use these characters even in single-quoted strings passed to the PowerShell CLI powershell.exe as (part of) the -Command argument(s).
Conversely, output redirections (e.g., > c:\path\to\log.txt) are not directly supported.
In the context of invoking the PowerShell CLI, this means:
With -File, you cannot use them on the command line and must instead perform them from within your script.
With -Command, however, you can use them, because it is then PowerShell that applies them (but note that Windows PowerShell's > operator creates UTF-16LE files).
(Even though cmd.exe isn't involved) references to environment variables using the same syntax form as in cmd.exe are expanded (e.g., %USERNAME%)
Caveat: You cannot escape such references:
%% doesn't work - the additional % is simply treated as a literal, and expansion still occurs; e.g., %%OS%% results in %Windows_NT%.
^% (accidentally) prevents expansion, but retains the ^ - the ^ doesn't escape; rather, it "disrupts" the variable name, in which case the token is left as-is; e.g., ^%OS^% results in ^%OS^%, i.e., is retained as-is.
The above applies to the commands as they must end up defined inside a scheduled task, as you would see or define them interactively in Task Scheduler (taskschd.msc).
Additionally, for creating a scheduled task from the command line / a PowerShell script / a batch file:
you must quote the command as a whole and
comply with the syntax rules of the calling shell regarding escaping and up-front string interpolation.
(You can only get away without quoting if the command consists of only a single word that needs no escaping, such the path to an executable that contains no spaces or special characters and to which no arguments are passed.)
When calling schtasks.exe[2], quote the /tr argument as a whole as follows:
from PowerShell, use "...", if you need to expand (string-interpolate) the command string up front; otherwise, use '...'.
Important: The need to escape nested " as \" applies in both cases, which in the case of outer "..." quoting means that nested " must be escaped as \`" (sic).
Surprisingly, schtasks.exe recognizes embedded '...' quoting and automatically translates it to "..." quoting - that is why your original command, "powershell.exe -file '..\Execute\execute.ps1'", worked, even though in direct invocation the PowerShell CLI does not support the use of '...' in combination with -File.
from cmd.exe (whether directly or from a batch file), you must use "...".
PowerShell examples:
The following PowerShell commands create and execute two run-once
scheduled tasks, named test1 and test2, that run when the next calendar minute starts, in the context of the calling user, visibly. (You'll have to remove these tasks manually afterwards.)
You may have to wait for up to 1 minute to see the invocation kick in, at which point a new console window pops up for each task.
# Create sample script test.ps1 in the current dir. that
# echoes its arguments and then waits for a keypress.
'"Hi, $Args."; Read-Host "Press ENTER to exit"' > test.ps1
# Find the start of the next calendar minute.
$nextFullMinute = ([datetime]::Now.AddMinutes(1).TimeOfDay.ToString('hh\:mm'))
# -File example:
# Invoke test.ps1 and pass it 'foo' as an argument.
# Note the escaped embedded "..." quoting around the script path
# and that with -File you can only pass literal arguments at
# invocation time).
schtasks.exe /create /f /tn test1 /sc once /st $nextFullMinute `
/tr "powershell -File \`"$PWD/test.ps1\`" foo" #`# (dummy comment to fix broken syntax highlighting)
# -Command example:
# Invoke test.ps1 and pass it $env:USERNAME as an argument.
# Note the '...' around the script path and the need to invoke it with
# &, as well as the ` before $env:USERNAME to prevent its premature expansion.
schtasks.exe /create /f /tn test2 /sc once /st $nextFullMinute `
/tr "powershell -Command & '$PWD/test.ps1' `$env:USERNAME"
"Tasks will execute at ${nextFullMinute}:00"
[1] Note that the Task Scheduler GUI allows you to configure a working directory, but this feature isn't available via the schtasks.exe utility.
[2] The same applies to values passed to the -Argument parameter of the New-ScheduledTaskAction PowerShell cmdlet, though note that the executable name/path is specified separately there, via the -Execute parameter.
By contrast, the Register-ScheduledJob cmdlet for creating scheduled PowerShell jobs accepts a script block as the command to run, which eliminates the quoting headaches.

Launch PowerShell from Command Prompt with custom prompt

I'm trying to open PowerShell with a customised prompt (for instance the UNIX shell prompt). I have tried:
powershell -noexit -command "& {function prompt {"$(pwd)$ "}}"
But it just starts powershell without the prompt I want. It does actually work in powershell itself. Could I get this to work or do I have to make a seperate file and do it through "-file"?
Not sure what the UNIX prompt defaults too but this should do what I think you want it to do.
powershell -noexit -command "function prompt {'{0}$ ' -f $pwd}"
If you use single quotes in the prompt function the $ doesn't get interpolated, and you don't have to worry about to many quotes.
SAVING THE PROMPT FUNCTION
Like any function, the Prompt function exists only in the current
session. To save the Prompt function for future sessions, add it to your
Windows PowerShell profiles. For more information about profiles,
see about_Profiles.
Here's how to create a new profile:
if (!(test-path $profile))
{new-item -type file -path $profile -force}
notepad $profile
Quoting on the command-line is tricky. Also, & runs a scriptblock in its own scope, so functions defined there don't "leak" out to the calling scope. The dot operator (also called dot-sourcing) is what you're looking for. This is what I got to work using backslashes to quote the strings.
powershell -noexit -command ". {function prompt {\"$(pwd)$ \"}}"
Add your custom prompt to your profile and it will load/run every time you start PowerShell.
Powershell customisation is always a bit tricky. Try adding a script with a method called prompt() like this:
function prompt() {
$myPrompt = "Ready>";
write-host -NoNewLine -ForegroundColor green $myPrompt
' '
}
Then call this in a profile, such as the one for all users:
%windir%\system32\Windows­PowerShell\v1.0\profile.ps1
Good luck!

Resources