Why working script fails when running in the background? - powershell

I have such script (here simplified):
while ($true)
{
Write-Host "Looping..."
Write-Host "Looping..." 6>> trash.txt
Start-Sleep -s 1
}
when I run it directly it works, but when I run it in the background:
Start-Job { .\sleeper.ps1 }
for a second it is seen as Running but shortly after as Failed and indeed file "trash.txt" is not created at all, so even one iteration is not executed.
What is wrong here?

I think the main issue is around the $PWD and -FilePath param, but I will list some info on Write-Host too:
Start-Job should be run with the -FilePath parameter. This is because {} is a ScriptBlock object, which is by default taken by the -ScriptBlock parameter, which you do not want. Example solution to that line: Start-Job -FilePath ./script.ps1
The $PWD or present working directory is, by default, the home directory / user profile of the current user when executed in a PowerShell job (Linux: $HOME // Windows: $Home/Documents). You can test this by executing a job simply with that variable (it may be $ENV:PWD on Windows?). Either way, it is likely not the same as the directory you are executing this in. trash.txt is being made and appended to, but it is being made in a different directory than your current directory. You will want your script to explicitly include an absolute path to the file being created, or give the script parameters that allow you to input the path at execution. Here is another StackOverflow article where a user had similar struggles, with two good solutions where one uses $args in the script and another uses the -InitializationScript parameter of Start-Job to set the $PWD: PowerShell Start-Job Working Directory
Often, Write-Host is low-priority as a selected output vs. using Write-Output / Write-Verbose / Write-Warning / Write-Error. More information about this can be found here: https://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/ - though, newer versions of PowerShell have added an information stream which I believe may make Write-Host output more accessible. More information on streams can be found with help about_Redirection

Related

PowerShell Start-Job not creating or amending files

I am new to PowerShell and this is the first time I am attempting to use a job. I am running into an issue where I have a part of a script that looks for a file, creates it if it doesn't exist and then amends the file, and when I run the script (not as a job) it executes correctly, but when I put it in a job, it doesn't amend the file.
A much simplified example of what I have is this:
Start-job -Name HostCheck -ScriptBlock {
ForEach ($Host in (Get-Content -Path .\HostFile.txt) {
Add-Content .\somefile.txt "`nWrite something on a new line for $Host"
} | Out-Null
}
# Removes job once it is finished
Get-Job -Name HostCheck | Wait-Job | Remove-Job
Now I have tried adding | Receive-Job after the | Out-Null, but that didn't seem to change anything.
I've seen people write the entire script-block to a variable and just use the variable instead, so I am curious if that is a requirement (but I wouldn't think so).
Also, this might matter, I open the script with a .bat file that escalates the PowerShell console to admin as well as setting the execution policy of the process to Bypass. Now it seems that everything that runs in that console session or is kicked off by that console session (several scripts get ran, this is just part of one of them) seems to inherit those settings, but being new with jobs, I don't know if it would also inherit those settings, or how I would force it to (if not).
I discovered the problem:
-Your current working directory is lost when starting a job so my relative path .\somefile.txt would default to C:\Users\[Username]\Documents instead of the location where the .\somefile.txt resides.
I can get around this by using an absolute path, or I think there is a way to pass arguments to a job, but if anyone knows a better way to do this, please feel free to comment.
Here's a workaround, cd to the current dir of the caller.
start-job { cd $using:pwd; pwd } | Receive-Job -wait -auto

Powershell Script not working out of ISE

I have a powershell Script that runs fine in the ISE, but when I run it directly from the .ps1 file it stops at this point:
Write-Host "Attempting to start FFMPEG Process with arguments:$ArgumentList::::" -ForegroundColor Green
Start-Process $SCRIPT:FFMPEGLocation $ArgumentList -Wait
I get the Write-Host printout, with the correct Argument List, but no window pops up for the start-process or anything. I made sure to use the SCRIPT scope variable for anything that is outside the function. It runs fine in ISE just not when I run it in Console. No errors either, and the $SCRIPT:FFMPEGLocation variable is a direct path to the exe to be executed.
Any help would be greatly appreciated, and if you need more let me know.
PSVersion 5.1.14393.1066
OSVersion 10.0.14393
As per comments, check the value passed to Start-Process to ensure it contains what you expect as the issue is likely to be with $SCRIPT:FFMPEGLocation.
The difference between the ISE and Console with regard to scope can be frustrating.
See this SuperUser post which explains the behaviour further
. You may also find this post helpful;
BartekB explains that F5 in the ISE actually dot-sources the script instead of calling it, and provides a function to run a clean session.
An alternative method which will also wait on the process that needs to be run:
& $EXE /arg 1 /arg 2 -etc 2>&1 | % { $_ }
This will execute the command and pass the args, redirect any errors to the output stream and then print that output (for some reason, I've had bad luck with actually getting output from commands)

Powershell Quotes Use asisstance

Need some expert help from you guru's out there (after 3 days of trying on my own lol). Here the portion of my script that's failing:
$yy=(get-date).Year
Invoke-Command -ComputerName *ipaddress* -Credential $moveitcred {
move-item -path "C:\iCApps\Dev\LetterGenerator\Letters\FTS\EDMS\*.txt" -destination "C:\Moveitft\Dev\Letters\Outbound" -force
Start-Process -FilePath "C:\Program Files\7-Zip\7z.exe" -ArgumentList "a -y C:\Moveitft\Dev\Letters\archive\Letters$yy_.zip C:\Moveitft\Dev\Letters\Outbound\*.*"
}
The move works fine. AND the zip works fine, except when I want to include the the year varible ($yy) in the argument list. Powershell doesn't want to populate the variable to adjust the file name. Either it bombs out citing a $null, or the job completes with just a blank where the $yy should be.
If i run just $yy from the prompt, it does return the correct year value, so I'm guessing it an improper use of quotes. But after 3 days of googling and trying various combinations on my own, I'm throwing in the towel. Any help/guidance would be so GREATLY appreciated :)
Change Letters$yy_.zip to either
Letters$($yy)_.zip
or
Letters${yy}_.zip.
Both of them work.
See: PowerShell subexpression and PowerShell variable names.
The problem is not about quoting is about variable scope. Inside the scriptblock you are executing, powershell knows nothing about the $yy var. So you need to tell it where to obtain the value. You could do that using the argumentList parameter. I see that you already are doing that but you are doing it wrong (move-item doesn't support that parameter). Look the following examples to grasp the concept, I think with that you will solve your problem:
# this is fine and simple
$yy="Hello scriptblocks!"
Invoke-Command -ScriptBlock { "whatever you want to run that use the an external var. $yy" } -ArgumentList $yy
# this is incorrect
Invoke-Command -ScriptBlock { "whatever you want to run that use the an external var. $yyCarefulWithThisText" } -ArgumentList $yy
# this shows how you could resolve the variable when you want to put text around
Invoke-Command -ScriptBlock { "whatever you want to run that use the an external var. $($yy)CarefulWithThisText" } -ArgumentList $yy
# output1: whatever you want to run that use the an external var. Hello scriptblocks!
# output2: whatever you want to run that use the an external var.
# output3: whatever you want to run that use the an external var. Hello scriptblocks!CarefulWithThisText
Tried the above, thank you! I think this might boil down to powershell version. I'm trying to execute this on a server running powershell v2.0, against another 2012 Server.
I've shortened down the code to just the line in question (so I can "F8" it to test the results) Here it is:
Invoke-Command -ComputerName ipaddress -Credential $moveitcred {
Start-Process -FilePath "C:\Program Files\7-Zip\7z.exe" -argumentlist "a -y C:\Moveitft\Dev\Letters\archive\Letters${yy}.zip C:\Moveitft\Dev\Letters\Outbound*.*"
}
Both of your suggestions "work", in that the command processes, however its still not reading the variable correctly. Here's what I get as a resultant .zip
The reason I'm thinking its powershell, is becuase if I copy/paste the same command into powershell v4.0 on my own production machine, you can instantly see by the color coding that the "$yy" is being considered a variable because it turns red - and then the code works ok on from my PS4.0 machine.
On the PSv2.0 server i'm trying to get this to run from, that same line for "-argumentlist" appears all maroon/brown - including the $yy portion. So its kind of a hint whether it will work or not for me.

Pass argument into Powershell

I have a powershell script that completes some tasks in Active Directory and MS Exchange. I need to pass in the Active Directory username from our call logging system. Having the Call log system pass the argument is simple.
The problem i am facing is having powershell read the argument into a variable.
I will provide a proof of concept example.
Here is a sample command passing the argument into powershell.
C:\Users\UserName\Desktop\Test.ps1 -ADusername "Hello World"
Here is the sample script:
Param([string]$adusername)
$adusername
pause
I am Expecting the following output:
Hello World
Press Enter to continue...:
This is the actual output:
Press Enter to continue...:
Just wrapping my head around this core concept will help me immensely. I was unable to find any examples or tutorials that worked when applied to my scenario. I apologize if this is a duplicate post, couldnt find anything on this site as well.
EDIT: per request, this is my full script: http://pastebin.com/ktjpLQek
I think you will have much better luck if you avoid trying to use params and call the script exactly that way.
It is possible, but paramaters work better if you either inline the scriptfile like:
. .\scriptFile.ps1
function "Hello World"
Staying closer to what you are doing however, you should be using $args and calling PowerShell (the exe directly)
If you call your scriptfile like: (I used the runbox)
powershell c:\Path\Folder\Script.ps1 "Hello World!"
and then replace your Param([string]$adusername) with:
$adUserName = $args[0]
write-host $adUserName
Additionally, this should work for you (to dissect):
Param([string]$ad)
Write-Host $args[0]
Write-Host $ad
Read-Host
pause
Call the script with the path,
powershell c:\Path\Folder\Script.ps1 "Hello World!" $ad = "JSmith"
If this does not work, you should ensure that your execution policy is set correctly. Get-ExecutionPolicy will tell you the current level. For testing you can set it very low with Set-ExecutionPolicy unrestricted
Add the following to the top of your script.
Param(
[Parameter(Mandatory=$true)]
[string]$Euser
)
Write-Host "Deactivating $EUser"
Calling example after cd to the script directory
.\ScriptName.ps1 -Euser "FOO" # Tab auto completion works
The following in a new script works for me.
Param([string]$servers)
"You chose $servers"
PS C:\scripts> .\Untitled1.ps1 "this test"
You chose this test
PS C:\scripts>

Call a PowerShell script in a new, clean PowerShell instance (from within another script)

I have many scripts. After making changes, I like to run them all to see if I broke anything. I wrote a script to loop through each, running it on fresh data.
Inside my loop I'm currently running powershell.exe -command <path to script>. I don't know if that's the best way to do this, or if the two instances are totally separate from each other.
What's the preferred way to run a script in a clean instance of PowerShell? Or should I be saying "session"?
Using powershell.exe seems to be a good approach but with its pros and cons, of course.
Pros:
Each script is invoked in a separate clean session.
Even crashes do not stop the whole testing process.
Cons:
Invoking powershell.exe is somewhat slow.
Testing depends on exit codes but 0 does not always mean success.
None of the cons is mentioned is a question as a potential problem.
The demo script is below. It has been tested with PS v2 and v3. Script names
may include special characters like spaces, apostrophes, brackets, backticks,
dollars. One mentioned in comments requirement is ability to get script paths
in their code. With the proposed approach scripts can get their own path as
$MyInvocation.MyCommand.Path
# make a script list, use the full paths or explicit relative paths
$scripts = #(
'.\test1.ps1' # good name
'.\test 2.ps1' # with a space
".\test '3'.ps1" # with apostrophes
".\test [4].ps1" # with brackets
'.\test `5`.ps1' # with backticks
'.\test $6.ps1' # with a dollar
'.\test ''3'' [4] `5` $6.ps1' # all specials
)
# process each script in the list
foreach($script in $scripts) {
# make a command; mind &, ' around the path, and escaping '
$command = "& '" + $script.Replace("'", "''") + "'"
# invoke the command, i.e. the script in a separate process
powershell.exe -command $command
# check for the exit code (assuming 0 is for success)
if ($LastExitCode) {
# in this demo just write a warning
Write-Warning "Script $script failed."
}
else {
Write-Host "Script $script succeeded."
}
}
If you're on PowerShell 2.0 or higher, you can use jobs to do this. Each job runs in a separate PowerShell process e.g.:
$scripts = ".\script1.ps1", ".\script2.ps1"
$jobs = #()
foreach ($script in $scripts)
{
$jobs += Start-Job -FilePath $script
}
Wait-Job $jobs
foreach ($job in $jobs)
{
"*" * 60
"Status of '$($job.Command)' is $($job.State)"
"Script output:"
Receive-Job $job
}
Also, check out the PowerShell Community Extensions. It has a Test-Script command that can detect syntax errors in a script file. Of course, it won't catch runtime errors.
One tip for PowerShell V3 users: we (the PowerShell team) added a new API on the Runspace class called ResetRunspace(). This API resets the global variable table back to the initial state for that runspace (as well as cleaning up a few other things). What it doesn't do is clean out function definitions, types and format files or unload modules. This allows the API to be much faster. Also note that the Runspace has to have been created using an InitialSessionState object, not a RunspaceConfiguration instance. ResetRunspace() was added as part of the Workflow feature in V3 to support parallel execution efficiently in a script.
The two instances are totally separate, because they are two different processes. Generally, it is not the most efficient way to start a Powershell process for every script run. Depending on the number of scripts and how often you re-run them, it may be affecting your overall performance. If it's not, I would leave everything AS IS.
Another option would be to run in the same runspace (this is a correct word for it), but clean everything up every time. See this answer for a way to do it. Or use below extract:
$sysvars = get-variable | select -Expand name
function remove-uservars {
get-variable |
where {$sysvars -notcontains $_.name} |
remove-variable
}