PowerShell Start-Job Working Directory
Asked Answered
M

6

35

Is there a way to specify a working directory to the Start-Job command?

Use-case:

I'm in a directory, and I want to open a file using Emacs for editing. If I do this directly, it will block PowerShell until I close Emacs. But using Start-Job attempts to run Emacs from my home directory, thus having Emacs open a new file instead of the one I wanted.

I tried to specify the full path using $pwd, but variables in the script block are not resolved until they're executing in the Start-Job context. So some way to force resolving the variables in the shell context would also be an acceptable answer to this.

So, here's what I've tried, just for completeness:

Start-Job { emacs RandomFile.txt }
Start-Job { emacs "$pwd/RandomFile.txt" }
Marivaux answered 8/2, 2010 at 19:42 Comment(0)
S
16

A possible solution would be to create a "kicker-script":

Start-Job -filepath .\emacs.ps1 -ArgumentList $workingdir, "RandomFile.txt"

Your script would look like this:

Set-Location $args[0]
emacs $args[1]

Hope this helps.

Sentient answered 11/2, 2010 at 18:8 Comment(5)
This worked out really well. The -ArgumentList is exactly the kind of thing I was looking for.Marivaux
Keep in mind that this will only work if $workingdir contains no spaces or other special characters.Pudens
@DiscoInfiltrator: Passing just $workingdir works fine in PowerShell, even with embedded spaces and other shell metacharacters (you're probably thinking of POSIX-like shells); verify with $dir = 'C:\Program Files\'; function foo() { set-location $args[0] }; foo $dir.Machree
@Filburt, why do you call it "kicker-script"?Halstead
@Halstead That's really just personal naming thing - a script to kick off another script or executable.Sentient
M
37

There is nice solution from comments section of some dude's post. Commenter suggests to use Init parameter to setup working directory of script block.

function start-jobhere([scriptblock]$block) {
    Start-Job -Init ([ScriptBlock]::Create("Set-Location '$pwd'")) -Script $block
}
Meliorism answered 30/6, 2013 at 9:32 Comment(4)
++ for the most generic solution: no helper script needed, no input argument that the background script block needs to know about and handle.Machree
this should have quotes around $pwd to handle spaces in the pathAnamorphism
@Anamorphism is correct, single quotes are needed because this is not a variable reference but a variable expansion in a string.Disaster
@Sander: Indeed (thanks for the correction - I'd missed that a string is being constructed). There's a tiny chance that this will break, however, given that filenames may contain ' instances, so a fully robust solution would be: "Set-Location '$($pwd -replace "'", "''")'"Machree
M
17

Update:

PowerShell [Core] 7.0 brings the following improvements:

  • Background jobs (as well as thread jobs and ForEach-Object -Parallel) now - sensibly - inherit the caller's current (filesystem) location.

  • A new -WorkingDirectory parameter allows you to specify a directory explicitly.


To summarize the problem (applies to Windows PowerShell and PowerShell Core 6.x)

  • A background job started with Start-Job does not inherit the current location (working directory).

    • It defaults to $HOME\Documents in Windows PowerShell, and $HOME in PowerShell Core.

    • If you're using PowerShell Core and you're targeting a file in the current directory, you can use the following, because the new ... & syntax by default runs the job in the current directory:
      emacs RandomFile.txt &

  • You cannot directly reference variables from the current session in a script block passed to Start-Job.

To complement jdmichal's own helpful answer and asavartsov's helpful answer, which still work well:

PSv3 introduced a simple way to reference the current session's variable values in a script block that is executed in a different context (as a background job or on a remote machine), via the $using: scope:

Start-Job { Set-Location $using:PWD; emacs RandomFile.txt }

Alternatively:

Start-Job { emacs $using:PWD/RandomFile.txt }

See about_Remote_Variables


Note:

  • This GitHub issue reports the surprising inability to use $using: in the script block passed to -InitializationScript, even though it works in the main script block (the implied -ScriptBlock parameter).
    • Start-Job -Init { Set-Location $using:PWD } { emacs RandomFile.txt } # FAILS
Machree answered 9/12, 2016 at 6:43 Comment(0)
S
16

A possible solution would be to create a "kicker-script":

Start-Job -filepath .\emacs.ps1 -ArgumentList $workingdir, "RandomFile.txt"

Your script would look like this:

Set-Location $args[0]
emacs $args[1]

Hope this helps.

Sentient answered 11/2, 2010 at 18:8 Comment(5)
This worked out really well. The -ArgumentList is exactly the kind of thing I was looking for.Marivaux
Keep in mind that this will only work if $workingdir contains no spaces or other special characters.Pudens
@DiscoInfiltrator: Passing just $workingdir works fine in PowerShell, even with embedded spaces and other shell metacharacters (you're probably thinking of POSIX-like shells); verify with $dir = 'C:\Program Files\'; function foo() { set-location $args[0] }; foo $dir.Machree
@Filburt, why do you call it "kicker-script"?Halstead
@Halstead That's really just personal naming thing - a script to kick off another script or executable.Sentient
V
7

try this

Start-Job -inputobject $pwd -scriptblock { emacs "$input/RandomFile.txt" }

Here $input is predefined variable that internally take the value of -inputobject parameter

Vaudevillian answered 20/11, 2010 at 6:19 Comment(0)
L
5

Start-Job is an overkill for what you need (running a command in the background). Use Start-Process instead:

Start-Process -NoNewWindow emacs RandomFile.txt

There are no issue with the current directory in this approach. I have also created a function to make this as simple as possible:

function bg() {Start-Process -NoNewWindow @args}

and then the invocation becomes:

bg emacs RandomFile.txt

This works on Windows 7 (Powershell v2).

Lighten answered 5/12, 2012 at 17:25 Comment(1)
But Start-Process persists after the terminal is closed?Poll
M
2

Just for completeness, here's the final script I implemented based on Filburt's answer, community-wiki style:

function Start-Emacs ( [string]$file )
{
    Start-Job -ArgumentList "$pwd\$file" { emacs $args[0] }
}
Marivaux answered 8/2, 2010 at 19:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.