I am creating a somewhat complex Post-Build scripting situation for my company, which will handle many moving parts. Using Powershell scripting provides a greater flexibility and so I've begun learning it.
However there is a output redirect issue installing a Java Certificate.
Now, everything works as expected. The check for the cert, the delete cert, even the install cert works fine - except for one little issue:
(This is the output from a successful run of the script)
[Command: C:\Program Files\Java\jre1.8.0_261\bin\keytool.exe] [Arguments: -list -storepass "storepass" -keystore "C:\Program Files\Java\jre1.8.0_261\lib\security\cacerts" -alias "ourcert.crt"] [Command: C:\Program Files\Java\jre1.8.0_261\bin\keytool.exe] [Arguments: -import -storepass "storepass" -keystore "C:\Program Files\Java\jre1.8.0_261\lib\security\cacerts" -alias "ourcert.crt" -file "\\unc\drive\share\path\ourcert.crt" -noprompt] Certificate was added to keystore <-- This line here Java Cert Installed in Store.
I'm using the "&$Command $args" method of invoking all external commands, and below is the script I'm running.
# This function is used all over the place to streamline the external command execution of
# KeyTool, Sonar Scanner, and MSBuild
function Invoke([String] $command, [String[]] $arguments)
{
Write-Host " [Commnad: $command]"
Write-Host " [Arguments: $arguments]"
&$command $arguments
}
function ValidateKeyTool()
{ # Our developers may or maynot have the same version of java so this is to find the most recent version on their system
$path = [System.IO.Directory]::GetFiles("C:\Program Files (x86)\Java", "keytool.exe", [System.IO.SearchOption]::AllDirectories);
$path = $path + [System.IO.Directory]::GetFiles("C:\Program Files\Java", "keytool.exe", [System.IO.SearchOption]::AllDirectories);
$path = $path | Sort-Object -Descending;
$script:KeyTool = $path | Select -First 1;
$script:KeyStore = (Join-Path -Path (Split-Path (Split-Path $path)) -ChildPath "lib\security\cacerts");
return ([System.IO.File]::Exists($KeyTool) -and [System.IO.File]::Exists($KeyStore));
}
function CheckCertExists()
{
if (ValidateKeyTool)
{
$args = @("-list", "-storepass", """storepass""", "-keystore", """$KeyStore""", "-alias", """ourcert.crt""");
Invoke $KeyTool $args | Out-Null;
return ($LastExitCode -eq 0)
}
else
{
throw "Unable to determine Java KeyTool or KeyStore";
}
}
function InstallCert()
{
if (!(CheckCertExists))
{
$args = @("-import", "-storepass", """storepass""", "-keystore", """$KeyStore""", "-alias", """ourcert.crt""", "-file", $CertFile, "-noprompt");
Invoke $KeyTool $args | Out-Null; #this DOESN'T Work, the Out-Null doesn't trap the output
if ($LastExitCode -eq 0)
{
Write-Host " Java Cert Installed in Store."
}
else
{
throw "Error occured attempting to Install the Java Cert into the Store."
}
}
else
{
Write-Host " Java Cert already installed."
}
}
All executions of the KeyTool with the "| Out-Null" trap the output as expected, for -list, for -delete, but NOT for -import. No matter what I've tried, the keytool with "-import" always produces that "Certificate was added to keystore" output message. I want to suppress it, and only go off of the $LastExitCode for success/failure.
Likely the cause is that the message is being outputted to another output stream. For ex. instead of outputting the message to the standard success stream (1), it may be outputting the message to the error stream (2) or warning stream (3), or another one. The pipelining to | Out-Null
will only handle the success stream, e.g.:
PS C:\> Write-Output "hi" | Out-Null
PS C:\>
PS C:\> Write-Warning "hi" | Out-Null
WARNING: hi
The blunt hammer approach to suppress all messages from all streams is to redirect all the streams like so:
Invoke $KeyTool $args *> $null
The "nicer" approach is if you know which stream the message is being sent to, (for ex. the Error stream (2)), you can redirect the individual streams. You do this by redirecting the Error stream (2) to the Success stream (1), and then the Success stream (1) to $null
:
Invoke $KeyTool $args 2>&1 > $null