I have put together a PSake (v2.0) build script, and the script is setting the $psake.build_success property as true even thought the call to MSBuild fails. Can anyone advise me on how to alter the script so that the $psake.build_success property will correctly return false when the MSBuild call fails?
My PSake build script is as follows:
properties {
$solutionFile = 'SOLUTION_FILE'
$buildSuccessfulMessage = 'Solution Successfully Built!'
$buildFailureMessage = 'Solution Failed to Build!'
$cleanMessage = 'Executed Clean!'
}
task default -depends BuildSolution
task BuildSolution
{
msbuild $solutionFile /t:Clean,Build
if ($psake.build_success)
{
$buildSuccessfulMessage
}
else
{
$buildFailureMessage
}
}
Is PowerShell's native $lastExitCode (i.e., WIn32 ExitCode) any use in the context? I'd be guessing that the built in one is only relevant when you're invoking a psake-related cmdlet.
i.e., replace the check with
if($lastexitcode -eq 0) {
Disclaimer: Only podcast level experience with psake :D
The issue seems to be that the call to MSBuild operation actually completes successfully, whilst the build operation it initiates fails. The way I was able to get around this was to pipe the output of the MSBuild call to a text file, and then parse the file for the string "Build Failed". If it contained the string, obviously the build failed.
My PSake build script is as follows:
properties {
$solutionFile = 'SOLUTION_FILE'
$buildSuccessfulMessage = 'Solution Successfully Built!'
$buildFailureMessage = 'Solution Failed to Build!'
$cleanMessage = 'Executed Clean!'
}
task default -depends Build
task Build -depends Clean {
msbuild $solutionFile /t:Build /p:Configuration=Release >"MSBuildOutput.txt"
}
task Clean {
msbuild $solutionFile /t:Clean
}
and in my calling script:
function Check-BuildSuccess()
{
return (! (Find-StringInTextFile -filePath .\MSBuildOutput.txt -searchTerm "Build Failed"))
}
function Is-StringInTextFile
(
[string]$filePath = $(Throw "File Path Required!"),
[string]$searchTerm = $(Throw "Search Term Required!")
)
{
$fileContent = Get-Content $filePath
return ($fileContent -match $searchTerm)
}
There is the psake Exec command that you can wrap msbuild with and a powershell error is thrown.
Exec {
msbuild $solutionFile "/p:Configuration=$buildConfiguration;Platform=$buildPlatform;OutDir=$tempOutputDirectory"
}
Neither $LastExitCode or $_ worked for me. This did however:
$buildArgs = "MySolution.sln", "/t:Build", "/p:Configuration=Debug"
$procExitCode = 0
$process = Start-Process -FilePath "msbuild" -ArgumentList $buildArgs -NoNewWindow -PassThru
Wait-Process -InputObject $process
$procExitCode = $process.ExitCode
#aha! msbuild sets the process exit code but powershell doesn't notice
if ($procExitCode -ne 0)
{
throw "msbuild failed with exit code $procExitCode."
}
P.S. If you use this in production I recommend adding -timeout handling to Wait-Process
Related
I'm trying to update an elevated PowerShell script that's using StartProcess on a BAT file that runs RunAs on PowerShell.exe to run another PowerShell script without elevation in order to clone a git repository so that the directory is created in a way that a normal non-elevated user will be able to use.
Elevated PS1: Start-Process
=> Elevated .BAT: RunAs /trustlevel:0x20000
=> Non-elevated PS1
This is failing in some environments and I can't figure out why so I'm trying to figure out how to capture stdout and stderr from all levels of this process, but I'm not seeing the error or any output. I can capture it down to the BAT file level, but I can't seem to see anything that's happening within the inner-most Powershell script.
This seems like an awful lot of work just to programmatically clone a Git repository from an elevated process. Is there a way to make this work or is there an easier way?
EDIT: Just learned that this solution was broken as of Windows 11 Update 22H2: https://superuser.com/questions/1749696/parameter-is-incorrect-when-using-runas-with-trustlevel-after-windows-11-22h2
but the workaround is to use the /machine switch when running RunAs.
I suggest simplifying your approach as follows:
Use synchronous invocation of runas.exe, via Start-Process -Wait, which obviates the need for an intermediate batch file, and the need for a named pipe (System.IO.Pipes.NamedPipeClientStream)
Let the runas.exe-launched PowerShell child process that runs test2.ps1 capture that script's output in a temporary file, which you can read after the Start-Process -Wait call returns.
test2.ps1 can then just produce output normally - no need for System.IO.Pipes.NamedPipeClientStream
Elevated PowerShell Script (test.ps1):
function IsAdmin{
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$Is64 = [Environment]::Is64BitOperatingSystem
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "Running with elevated privileges. (64-bit=$Is64)"
} else {
Write-Output "Running without elevated privileges. (64-bit=$Is64)"
}
}
IsAdmin
# Create a temporary file in which to capture the output from the
# PowerShell child process launched by runas.exe.
$outFile = New-TemporaryFile
# Use Start-Process -Wait to directly invoke runas.exe,
# which doesn't just wait for runas.exe ITSELF to exit, but also
# waits for its CHILD processes.
# This ensures that execution is blocked until the other PowerShell script exits too.
Start-Process -Wait runas.exe #"
/machine:amd64 /trustlevel:0x20000 "powershell -c & \"$PSScriptRoot\test2.ps1\" -drive C:\ *> \"$outFile\""
"#
# Now $outFile contains all output produced by the other PowerShell script.
Write-Verbose -Verbose "Output from the runas.exe-launched PowerShell script:"
Get-Content -LiteralPath $outFile
$outFile | Remove-Item # Clean up.
Non-Elevated PowerShell Script (test2.ps1):
param([string]$drive)
function IsAdmin{
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$Is64 = [Environment]::Is64BitOperatingSystem
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "Running with elevated privileges. (64-bit=$Is64)"
} else {
Write-Output "Running without elevated privileges. (64-bit=$Is64)"
}
}
function Setup-Test{
Write-Output "Testing Powershell with Parameter Drive=$drive"
git config --global user.name
cd bob
Write-Error "Error Line 1
Error Line 2"
Write-Error "Error Line 3"
$d = 3/0
Write-Output "Done Testing Powershell"
}
IsAdmin
Setup-Test
This can be solved with a named pipe.
Elevated PowerShell Script (test.ps1)
function IsAdmin{
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$Is64 = [Environment]::Is64BitOperatingSystem
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "Running with elevated privileges. (64-bit=$Is64)"
} else {
Write-Output "Running without elevated privileges. (64-bit=$Is64)"
}
}
IsAdmin
Write-Output "Running $PSScriptRoot\test.bat"
Start-Process -FilePath "$PSScriptRoot\test.bat" -ArgumentList "C:\" -NoNewWindow
$np = new-object System.IO.Pipes.NamedPipeClientStream('.','SAMPipe', [System.IO.Pipes.PipeDirection]::In,[System.IO.Pipes.PipeOptions]::None,[System.Security.Principal.TokenImpersonationLevel]::Impersonation)
$np.Connect()
$sr = new-object System.IO.StreamReader($np)
while ($l=$sr.ReadLine()) {
Write-Output $l
}
$sr.Close()
$np.Close()
BAT file in the middle to de-elevate (test.bat)
runas /machine:amd64 /trustlevel:0x20000 "powershell -command %~dp0test2.ps1 -drive %1 >dummy.txt"
Non-Elevated PowerShell Script (test2.ps1)
param([string]$drive)
function IsAdmin{
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$Is64 = [Environment]::Is64BitOperatingSystem
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "Running with elevated privileges. (64-bit=$Is64)"
} else {
Write-Output "Running without elevated privileges. (64-bit=$Is64)"
}
}
function Setup-Test{
Write-Output "Testing Powershell with Parameter Drive=$drive"
git config --global user.name
cd bob
Write-Error "Error Line 1
Error Line 2"
Write-Error "Error Line 3"
$d = 3/0
Write-Output "Done Testing Powershell"
}
$np = New-Object System.IO.Pipes.NamedPipeServerStream('SAMPipe',[System.IO.Pipes.PipeDirection]::Out)
$np.WaitForConnection()
$sw = New-Object System.IO.StreamWriter($np)
$sw.WriteLine('Begin Non-Elevated Process Pipe')
Invoke-Command -ScriptBlock {
try {
IsAdmin
Setup-Test
} catch {
Write-Error $_
}
} -ErrorVariable errVar -OutVariable out
foreach ($line in $out){
$sw.WriteLine($line)
}
foreach ($line in $errVar) {
$sw.WriteLine($line)
}
$sw.WriteLine('End Non-Elevated Process Pipe')
$sw.Close()
$np.Close()
Output
Running with elevated privileges. (64-bit=True)
Running C:\Users\bmarty\source\PowerShellTest\test.bat
C:\Users\bmarty\source\PowerShellTest>runas /machine:amd64 /trustlevel:0x20000 "powershell -command C:\Users\bmarty\source\PowerShellTest\test2.ps1 -drive C:\ >dummy.txt"
Begin Non-Elevated Process Pipe
Running without elevated privileges. (64-bit=True)
Testing Powershell with Parameter Drive=C:\
Ben Marty
Cannot find path 'C:\Users\bmarty\source\PowerShellTest\bob' because it does not exist.
Error Line 1
Error Line 2
Error Line 3
Attempted to divide by zero.
System.Management.Automation.RuntimeException: Attempted to divide by zero. ---> System.DivideByZeroException: Attempted to divide by zero.
--- End of inner exception stack trace ---
at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
Attempted to divide by zero.
Attempted to divide by zero.
End Non-Elevated Process Pipe
Done running
I don't understand why the output of git config only appears in the output if I include >dummy.txt in the BAT file.
Try working with ACLs instead. You can set that up on the parent directory so you don't even need to run the script in an elevated context.
Just set up a "gitclone" account that can write into the repository parent directory and then add the rest of the users as read+execute.
The rest will come automagically through inheritance.
Then run script as that "gitclone" user.
Log file not being generated while running a PowerShell script via Windows Task Scheduler.
Code as below:
function check-cert
{
$cmd = "Certutil -crl"
Invoke-Expression $cmd
if($LASTEXITCODE -eq '0')
{
Write-Output $LASTEXITCODE
}
else
{
$output = $LASTEXITCODE
$date = (Get-Date).ToString()
$result = $date + " " + $output
$result | Out-File "C:\users\admin\Documents\Powershell\crllog.txt" -Append
Write-Host "crl failed to publish"
}
}
check-cert
Could you please help me in getting the log file while running the script via Task Scheduler?
When I run the PowerShell script using the PowerShell editor, the output file gets generated. But when scheduled via Windows Task Scheduler it doesn't.
I have a powershell script that defines $ErrorActionPreference = "Stop"
But i also have a start-process call that target a process that returns a non-standard exit code on success (1 instead of 0).
As a result of this, the script is failing even when the start-process is ok.
I tried to append the -ErrorAction "Continue" parameter in the start-process call but it didn't solve the problem.
The problematic line looks like this:
$ErrorActionPreference = "Stop"
...
start-process "binary.exe" -Wait -ErrorAction "Continue"
if ($LastExitCode -ne 1)
{
echo "The executable failed to execute properly."
exit -1
}
...
How could I prevent start-process from making the whole script fail.
Start-Process doesn't update $LASTEXITCODE. Run Start-Process with the -PassThru parameter to get the process object, and evaluate that object's ExitCode property:
$ErrorActionPreference = "Stop"
...
$p = Start-Process "binary.exe" -Wait -PassThru
if ($p.ExitCode -ne 1) {
echo "The executable failed to execute properly."
exit -1
}
I'm trying to create build scripts with psake for our company C# project in .NET 3.5, but when I run it, it fails with errors
default parameter specifiers are not permitted
I google out and it looks like it is a problem of .NET 3.5 that does not allow default parameters in functions.
However it is strange that same project built with msbuild.exe has succeeded. I tried to set $framework variable of psake to '3.5x86' and '3.5x64' but none of that helps.
Do you have any idea what is wrong with psake or if there is an secret variable that I'm missing?
My psake script:
$scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$framework = '3.5x86'
$serverSln = $scriptDir+'\..\Server\Server.sln'
$clientSln = $scriptDir+'\..\Client\Client.sln'
$outDirServer = $scriptDir+'\..\Binaries\Server'
$outDirClient = $scriptDir+'\..\Binaries\Client'
task default -depends Full
task Full -depends Clean, BuildServer, BuildClient #, BuildAccs, DeployRelease
task BuildServer {
#Framework '3.5'
exec { msbuild $serverSln /t:Build /p:Configuration=Release /v:quiet }
}
task BuildClient {
#Framework '3.5'
exec { msbuild $clientSln /t:Build /p:Configuration=Deploy /v:quiet }
exec { msbuild $clientSln /t:Build /p:Configuration=Release /v:quiet }
}
task Clean {
Write-Host Cleaning
if (Test-Path $outDirClient)
{
rd $outDirClient -rec -force | out-null
}
if (Test-Path $outDirServer)
{
rd $outDirServer -rec -force | out-null
}
}
Output of script:
$scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$framework = '3.5x86'
$serverSln = $scriptDir+'\..\Server\Server.sln'
$clientSln = $scriptDir+'\..\Client\Client.sln'
$outDirServer = $scriptDir+'\..\Binaries\Server'
$outDirClient = $scriptDir+'\..\Binaries\Client'
task default -depends Full
task Full -depends Clean, BuildServer, BuildClient #, BuildAccs, DeployRelease
task BuildServer {
#Framework '3.5'
exec { msbuild $serverSln /t:Build /p:Configuration=Release /v:quiet }
}
task BuildClient {
#Framework '3.5'
exec { msbuild $clientSln /t:Build /p:Configuration=Deploy /v:quiet }
exec { msbuild $clientSln /t:Build /p:Configuration=Release /v:quiet }
}
task Clean {
Write-Host Cleaning
if (Test-Path $outDirClient)
{
rd $outDirClient -rec -force | out-null
}
if (Test-Path $outDirServer)
{
rd $outDirServer -rec -force | out-null
}
}
The $framework variable is deprecated in latest versions, you should use the framework function instead. But it apparently does not work. It looks like a bug in PSake.
I finally found a bug between keyboard and chair. After I deleted obj files of project that was created by .NET 4.0, the MSBuild.exe keeps failing on same error. So there is no bug in psake and it is still great :)
I would like to inject command line parameters into my psake build script like:
.\build.ps1 Deploy environment="development"
But psake will treat every argument as a Task and will answer "task does not exists"
Is it possible to inject command line arguments in psake?
build.ps1 -->
Import-Module '.\psake.psm1'
Invoke-psake '.\tasks.ps1' $args
Remove-Module psake
The latest release of psake now supports passing parameters to Invoke-psake, e.g.
Invoke-psake .\parameters.ps1 -parameters #{"p1"="v1";"p2"="v2"}
This feature has just been added. :)
A global variable will solve my problem for now and with only one reference to $global:arg_environent it will be easy to change if i find a better way to inject the properties.
build.ps1
param(
[Parameter(Position=0,Mandatory=0)]
[string]$task,
[Parameter(Position=1,Mandatory=0)]
[string]$environment = 'dev'
)
clear
$global:arg_environent = $environment
Import-Module .\psake.psm1
Invoke-psake tasks.ps1 $task
Remove-Module psake
tasks.ps1
properties {
$environment = $global:arg_environent
}
task default -depends Deploy
task Deploy {
echo "Copy stuff to $environment"
}
I'm no expert, but I don't think it is possible to pass arguments to Invoke-Psake. Looking on the latest source for Psake the params for the Invoke-Psake function are:
param(
[Parameter(Position=0,Mandatory=0)]
[string]$buildFile = 'default.ps1',
[Parameter(Position=1,Mandatory=0)]
[string[]]$taskList = #(),
[Parameter(Position=2,Mandatory=0)]
[string]$framework = '3.5',
[Parameter(Position=3,Mandatory=0)]
[switch]$docs = $false
)
There are 4 parameters, your build file, a list of tasks, the .NET framework version, whether to output docs of your tasks. I'm new to powershell and psake and I'm trying to do the same thing, I am experimenting with doing something like this in my script to achieve the same thing:
properties {
$environment = "default"
}
task PublishForLive -precondition { $environment = "Live"; return $true; } -depends Publish {
}
task PublishForStaging -precondition { $environment = "Staging"; return $true; } -depends Publish {
}
task Publish {
Write-Host "Building and publishing for $environment environment"
#Publish the project...
}
Then calling psake with PublishForLive or PublishForStaging, whichever I need:
powershell -NoExit -ExecutionPolicy Unrestricted -Command "& {Import-Module .\tools\psake\psake.psm1; Invoke-psake .\psake-common.ps1 PublishForLive }"
But it doesn't seem to work for me! Setting the $environment variable in the task precondition seems to have no effect. Still trying to make this work...