How to capture console app output in Octopus custom PowerShell script? - powershell

I simply created an console app with argument number check at the very beginning. And after the package is deployed, in the deployment PowerShell script part, I directly call this app with no argument to test the script. It seems Octopus just captures the exit code and show there is no output from the app captured in task log at all.
static void Main(string[] args)
{
if (args.Length < 4)
{
Console.WriteLine("Invalid argument number");
Environment.ExitCode = -1;
return;
}
}
However, if I simply put "echo 'test'" or even just 'test' string in the script, the output was captured in Octopus deployment task log. Any idea what is the correct way to log console app in the script? Thanks.

Sorry, it is not fault of Octopus, It is actually the console app was built for .Net framework 4.6.1 but the tentacle server is only having 4.5.2. When I run the app on that server through remote desktop, it pops up error message saying 4.6.1 .Net framework is missing. Rebuild the app with 4.5.2 fixed this issue. However it was really hard to find this out because Octopus task log has nothing related to that. Hope this would help someone else in the future.

Create a file named "yourpowershellfile.ps1" or Create e deployment step "Run a script".
Try this powershell with OctopusDeploy,
$FullPath = "C:\MyFolder"
if ($OctopusEnvironmentName -ceq 'Development')
{
Write-Host "Console app will be execute"
& "$FullPath\yourconsolefile.exe" | Write-Host
Write-Host "Console app execution has finied"
}
You should try "Write-Output" instead of "Write-Host" Review the Octopus deploy task log.

If you found this question because you really need to get the console output following executables run in your Octopus deployment steps you can use the following code. It took a bit of research to write the following function I happily re-use accross Octopus steps where I need the console output:
Function Invoke-CmdCommand{
param(
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[ValidateScript({(Test-Path $_.Trim('"').Trim(''''))})]
[string]$Executable,
[string]$Parameters = '',
[switch]$CmdEscape
)
BEGIN{
Write-Verbose "Start '$($MyInvocation.Mycommand.Name)'"
$nl = [Environment]::NewLine
$exitCode = 0
$cmdOutput = [string]::Empty
# next line wrap string in quotes if there is a space in the path
#$Executable = (Format-WithDoubleQuotes $Executable -Verbose:$([bool]($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent)))
$command = "{0} {1}" -f $Executable, $Parameters
Write-Verbose "COMMAND: $command"
$terminatePrompt = "/C" # https://ss64.com/nt/cmd.html
$comSpec = $env:ComSpec
if($CmdEscape.IsPresent){
$command = "`"$command`""
Write-Verbose "ESCAPED COMMAND: $command"
}
}
PROCESS{
$cmdResult = .{
# script block exec: dot does not create local scope as opposed to ampersand
.$comSpec $terminatePrompt $command '2>&1' | Out-String | Tee-Object -Variable cmdOutput
return $LastExitCode
}
$exitCode = $cmdResult[$cmdResult.length-1]
if($exitCode -ne 0){
Write-Host "FAILED with ExitCode: $exitCode; ERROR executing the command:$nl$command$nl" -ForegroundColor Red
Write-Host "ERROR executing the command:$nl$command" -ForegroundColor Yellow
}else{
Write-Host "SUCCESSFULLY executed the command:$nl$command$nl"
}
}
END{
if($Host.Version.Major -le 3){
return ,$cmdOutput # -NoEnumerate equivalent syntax since it is not available in version 2.0
}else{
Write-Output -NoEnumerate $cmdOutput
}
Write-Verbose "End '$($MyInvocation.Mycommand.Name)'"
}
}
USAGE:
Invoke-CmdCommand -Executable (Join-Path (Split-Path $env:ComSpec) ping.exe) -Parameters 'localhost'
OUTPUT:
Pinging localhost [::1] with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms

We just add a Deploy.ps1 to the package, with the following code:
& .\MyCompany.Foo.Bar.exe 2>&1
Resources:
In the shell, what does " 2>&1 " mean?

Related

Powershell Script working fine in Visual Code but fails running from Terminal

I'm working on writing a script which will run from AzDo Pipeline to disable F5 WebServers. Below script works fine in Visual Code and does disable the server as expected . But when running from the terminal or PS window fails with the below error . Can someone please help.
$ServerInput = 'server1.abc.com'
$BIGIPBaseURL = "https://ser-f5-1.prod.abc.com"
$usr = "nilesh"
$SecurePassword='P#assword'
Write-Host "Starting the Script..."
# Initialize variables
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$BIGIPToken = $null
Write-Host -ForegroundColor Green " done!"
$DisableWebServers = $true
# Initialize functions
Write-Host "Initializing functions..." -NoNewline
$PSVersionTable
function Disable-BIGIPNode([string]$NodeName) {
# servers should use the Disable-BIGIPTelcoNode() function
Write-Host "In the Disable function"
if ($NodeName -match "(?i).*telco.*") {
Write-Host -ForegroundColor Yellow "WARNING: `"$($NodeName.ToUpper().Split('.')[0])`" is in the wrong list. telcoo hosts should be added to the TelcoServers list in your input file."
BREAK
}
else {
if ($BIGIPToken -eq $null) {
Write-Host "Now will enter the Open-Session"
Open-BIGIPSession
}
Write-Host "Disabling node `"$($NodeName.ToUpper().Split('.')[0])`" in BIG-IP..." -NoNewline
$WebRequestInput = #{
body = #{
"session" = "user-disabled"
} | ConvertTo-Json
uri = $($BIGIPBaseURL) + "/mgmt/tm/ltm/node/~Common~" + $NodeName.ToLower()
headers = #{
"Content-Type" = "application/json"
"X-F5-Auth-Token" = "$BIGIPToken"
}
method = "PATCH"
}
Write-Host $WebRequestInput
Write-Host $WebRequestInput.body
try {
Write-Host "In the final try block"
$Request = Invoke-WebRequest #WebRequestInput -UseBasicParsing -SkipCertificateCheck
}
catch {
Write-Host -ForegroundColor Red " failed!"
Write-Host -ForegroundColor Red ($_.ErrorDetails | ConvertFrom-Json).Message
}
Write-Host -ForegroundColor Green " done!"
$global:ZabbixRequestID++
}
}
function Open-BIGIPSession() {
Write-Host "Authenticating with BIG-IP API..." -NoNewline
$WebRequestInput = #{
body = #{
username = "$usr"
password = "$SecurePassword"
loginProviderName = "tmos"
} | ConvertTo-Json
uri = $ScriptInput.BIGIPBaseURL + "/mgmt/shared/authn/login"
headers = #{
"Content-Type" = "application/json"
}
method = "POST"
}
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$Request = Invoke-WebRequest #WebRequestInput -UseBasicParsing -SkipCertificateCheck
}
catch {
Write-Host -ForegroundColor Red " failed!"
Write-Host -ForegroundColor Red ($_.ErrorDetails | ConvertFrom-Json).Message
EXIT 1
}
Write-Host -ForegroundColor Green " done!"
$global:BIGIPToken = ($Request.Content | ConvertFrom-Json).token.token
}
if ($DisableWebServers) {
Write-Host "Starting the main Methord "
foreach ($Server in $($ServerInput)) {
Disable-BIGIPNode -NodeName $Server
}
}
The PowerShell version is PSVersion 7.2.2
Disabling node "SAC-DEV-WEB2" in BIG-IP...System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry
{
"session": "user-disabled"
}
In the final try block
failed!
ConvertFrom-Json: C:\Temp\Testing.ps1:49:64
Line |
49 | … Host -ForegroundColor Red ($_.ErrorDetails | ConvertFrom-Json).Messag …
| ~~~~~~~~~~~~~~~~
| Conversion from JSON failed with error: Additional text encountered after finished reading JSON content: U. Path '', line
| 3, position 4.
Its working fine when running from VsCode but fails if called with the file name from the same terminal
like .\Testing.ps1
Please help
Your incidental problem is that the true error message is being obscured by a follow-up error that results from attempting to parse the error record's .ErrorDetails property as JSON, which it isn't. (You report that examining the true error reveals a 401 authentication error).
I have no specific explanation for the difference in behavior you're seeing between running in Visual Studio Code vs. in a regular PowerShell console, but I have a guess:
Your Visual Studio Code session in the so-called PowerShell Integrated Console may have lingering state from earlier debugging runs, which may mask a bug in your script.
Restarting Visual Studio Code should clarify whether that is the case, but there's also a way to configure the PowerShell extension so that the problem doesn't arise to begin with - see below.
By default, code you run (debug) via the Visual Code PowerShell extension executes in the same PowerShell session, directly in the global scope.
That is, running a script being edited, say, foo.ps1, in the debugger is effectively the same as invoking it with . .\foo.ps1, i.e. it is in effect dot-sourced.
Therefore, a given debugging run can be affected by earlier runs, because the state of earlier runs lingers.
This can result in bugs going undetected, such as in the following example:
Say your script defines variable $foo and uses it throughout the script. If you debug your script at least one, $foo is now defined in the PowerShell session in the PowerShell Integrated Console.
Say you then change the name to $bar, but you forget to also replace (all) references to $foo with $bar.
Your script is now effectively broken, but you won't notice in the same session, because $foo is still around from earlier debugging runs.
However, running the script from a regular PowerShell console would surface the problem.
The obsolescent Windows PowerShell ISE exhibits the same unfortunate behavior, invariably so, but fortunately there is a solution for the PowerShell extension - see next point.
You can avoid this problem by activating the Create Temporary Integrated Console setting (via File > Preferences > Settings or Ctrl+,), which ensure that every debugging run creates a new, temporary session to run in, which starts with a clean slate:
Whenever a new temporary session is started, any previous one is automatically discarded.
A temporary session has prefix [TEMP] in the list of running shells in the integrated terminal.
You pay a performance penalty, because a new PowerShell session must be created for every run, and you lose the previous session's display output - but I suspect avoiding the pitfalls of lingering state is worth the price.
Note that, in a given temporary session, the dot-sourced invocation described above still applies, but with the lingering-state problem out of the picture, it can now be considered an advantage: After the script finishes, and before the temporary session is replaced with a new one, the variables and functions defined in the script's top-level scope are then available for inspection.

How can I check if the PowerShell profile script is running from an SSH session?

I'm trying to work around a bug in Win32-OpenSSH, where -NoProfile -NoLogo is not respected when using pwsh.exe (Core) and logging in remotely via SSH/SCP. One way (of several) I tried, was to add the following in the very beginning of my Microsoft.PowerShell_profile.ps1 profile.
function IsInteractive {
$non_interactive = '-command', '-c', '-encodedcommand', '-e', '-ec', '-file', '-f'
-not ([Environment]::GetCommandLineArgs() | Where-Object -FilterScript {$PSItem -in $non_interactive})
}
# No point of running this script if not interactive
if (-not (IsInteractive)) {
exit
}
...
However, this didn't work with a remote SSH, because when using [Environment]::GetCommandLineArgs() with pwsh.exe, all you get back is:
C:\Program Files\PowerShell\6\pwsh.dll
regardless whether or not you are in an interactive session.
Another way I tried, was to scan through the process tree and look for the sshd parent, but that was also inconclusive, since it may run in another thread where sshd is not found as a parent.
So then I tried looking for other things. For example conhost. But on one machine conhost starts before pwsh, whereas on another machine, it starts after...then you need to scan up the tree and maybe find an explorer instance, in which case it is just a positive that the previous process is interactive, but not a definite non-interactive current process session.
function showit() {
$isInter = 'conhost','explorer','wininit','Idle',
$noInter = 'sshd','pwsh','powershell'
$CPID = ((Get-Process -Id $PID).Id)
for (;;) {
$PNAME = ((Get-Process -Id $CPID).Name)
Write-Host ("Process: {0,6} {1} " -f $CPID, $PNAME) -fore Red -NoNewline
$CPID = try { ((gwmi win32_process -Filter "processid='$CPID'").ParentProcessId) } catch { ((Get-Process -Id $CPID).Parent.Id) }
if ($PNAME -eq "conhost") {
Write-Host ": interactive" -fore Cyan
break;
}
if ( ($PNAME -eq "explorer") -or ($PNAME -eq "init") -or ($PNAME -eq "sshd") ) {
# Write-Host ": non-interactive" -fore Cyan
break;
}
""
}
}
How can I check if the profile script is running from within a remote SSH session?
Why am I doing this? Because I want to disable the script from running automatically through SSH/SCP/SFTP, while still being able to run it manually (still over SSH.) In Bash this is a trivial one-liner.
Some related (but unhelpful) answers:
Powershell test for noninteractive mode
How to check if a Powershell script is running remotely

TFS2015 Powershell on Target Machine - Set a variable in the release process / Find a free port on a remote server

I want to get a free port from my deployment server. To achieve this I run this powershell-script and I want to set the passed in variable in my release process. Is there a good solution to set a variable in my release process which was calculated on a remote server (the deployment target in this case).
I also tried to use write-host to set the variable but than the script crashed in my release process..
Param(
[string]$variableName
)
$port = 1000
for($i=1000;$i -le 65000;$i++){
$free = netstat -o -n -a | findstr ":$i"
IF([string]::IsNullOrEmpty($free)) {
$port = $i
break;
}
}
Write-Output "##vso[task.setvariable variable=$variableName]$port"
Or is there a better way to find a free port on a remote deployment target?
For anyone needing a solution to set a build/release variable from a "PowerShell on Target Machine" task this worked for me:
Write-Verbose ("##vso[task.setvariable variable=SomeVariable]{0}" -f $theValue) -verbose
Based on my test, set variable isn’t working in PowerShell On Target Machine task.
You can store variable value in a file, then read data from that file.
here is the correct script. I just use the normal powershell task and run the following script, than I can set the variable correctly.
Param(
[string]$remoteServer,
[string]$variableName
)
Write-Host "Parameters: Server: " $remoteServer
$port = 80
$usedPorts = invoke-command -ComputerName $remoteServer -ScriptBlock { return Get-WebBinding }
Write-Host "Got Ports: $usedPorts"
for($i=8000;$i -le 65000;$i++){
$free = $usedPorts | findstr ":$i"
IF([string]::IsNullOrEmpty($free)) {
$port = $i
break;
}
}
Write-Host "Found port: " $port
Write-Host "##vso[task.setvariable variable=$variableName;]$($port)"
exit 0

Error Message from Powershell Script mail to EmailAddress

I have constructed an script for our Dataloader in Salesforce:
**# The Function for the Check Error File**
function CheckErrorFile {
Param ([string]$perrorfilepath)
$csvlines = (Get-Content $perrorfilepath | Measure-Object -line).Lines
# header is always 1 row so we look for > 1 rows
if ($csvlines -gt 1)
{
$errorCount = $csvLines - 1
$errorMessage = [string]::Format("** errors ** {0} failed rows in {1}", $errorCount, $perrorfilepath)
# this will print red error message but not stop execution
write-error $errorMessage
}
}
**# set up locations/variables for data loader**
$dataloader = "C:\Program Files (x86)\salesforce.com\Data Loader\bin\process.bat"
$dlconfig = "U:\Projekte\Salesforce\Dataloader-Test-Infor-Files\Example-Migration-SF-Test\DataLoaderConfig"
set-location "C:\Program Files (x86)\salesforce.com\Data Loader\bin"
$dlLogFolder = "U:\Projekte\Salesforce\Dataloader-Test-Infor-Files\Example-Migration-SF-Test\Logs\"
**# execute the data loader**
& $dataloader $dlconfig testsf-delete
**# check for errors in the error.csv file**
CheckErrorFile ("{0}error_testsf.csv" -f $dlLogFolder)
So far so good it think
But what i want to is that the Error Code which comes Back from CheckErrorFile Command should sent to an Email Address.
So i had think about 2 Considerations:
when if error sent to mail
pack the error output in a variable and sent to mail adress
But i'm not so good at Powershell that i know how i have to integrate this in my script.
I found a site with an send trigger when a eventlog has changed but I'm not sure if i can use this for my purposes and how i have to integrate this:
Powershell Email when Eventlog is Changed
May someone could help
thnks

Powershell Job Memory Consumption Issue

I've been struggling with this for a week now and have exhausted all the methods and options I have found online. I am hoping someone here will be able to help me out with this.
I am using powershell to start 8 jobs, each job running FFmpeg to stream a 7 minute file to a remote RTMP server. This is pulling from a file on the disk and each job uses a different file. The command is in a do while loop so that it is constantly restreaming.
This is causing the shell I launched the jobs from to accumulate a massive amount of memory, consuming all that it can. In 24 hours it consumed 30 of the 32 GB of my server.
Here is my launch code, any help would be appreciated.
start-job -Name v6 -scriptblock {
do { $d = $true; $f = Invoke-Expression -Command "ffmpeg -re -i `"C:\Shares\Matthew\180p_3000k.mp4`" -vcodec copy -acodec copy -f flv -y rtmp://<ip>/<appName>/<streamName>"; $f = $null }
while ($d = $true)
}
I've tried to receive the jobs and pipe it to out-null, I've tried setting $f to $null before starting the do while loop, and some other things I found online but to no avail. Thanks everyone for your time!
Better late than never I guess. I've had the same problem with huge memory consumption when running ffmpeg in Powershell jobs. The core of the issue is that a Powershell job will store any/all output into memory, and ffmpeg is extremely happy to log output to both standard output and standard error streams.
My solution was to add the parameter "-loglevel quiet" to ffmpeg. Alternatively you could redirect both the standard and error streams to null (it's not enough to redirect just the standard stream). For more on how to redirect the standard streams, refer to this question: Redirection of standard and error output appending to the same log-file
There are many ways to invoke a PowerShell script or an expression.
One of my favorites is using RunspacePool. In a RunspacePool each task is started with a new runspace. When the task completes the runspace is disposed of. This should keep memory consumption fairly constant. It is not the easiest method to get your head around, but here is a bare-bone example that might work for you.
$Throttle = 10 # Maximum jobs that can run simultaneously in a runpool
$maxjobs = 8 # Maximum jobs that will run simultaneously
[System.Collections.ArrayList]$jobs = #()
$script:currentjobs = 0
$jobindex = 0
$jobaction = "ffmpeg -re -i `"C:\Shares\Matthew\180p_3000k.mp4`" -vcodec copy -acodec copy -f flv -y rtmp://<ip>/<appName>/<streamName>"
$sessionstate = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$runspace = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host)
$runspace.Open()
function QueueJob{
param($jobindex)
$job = "" | Select-Object JobID,Script,Errors,PS,Runspace,Handle
$job.JobID = $jobindex
$job.Script = $jobaction
$job.PS = [System.Management.Automation.PowerShell]::create()
$job.PS.RunspacePool = $runspace
[void]$job.PS.AddScript($job.Script)
$job.Runspace = $job.PS.BeginInvoke()
$job.Handle = $job.Runspace.AsyncWaitHandle
Write-Host "----------------- Starting Job: $("{0:D4}" -f $job.JobID) --------------------" -ForegroundColor Blue
$jobs.add($job)
$script:currentjobs++
}
Function ServiceJobs{
foreach($job in $Jobs){
If ($job.Runspace.isCompleted) {
$result = $job.PS.EndInvoke($job.Runspace)
$job.PS.dispose()
$job.Runspace = $null
$job.PS = $null
$script:currentjobs--
Write-Host "----------------- Job Completed: $("{0:D4}" -f $job.JobID) --------------------" -ForegroundColor Yellow
Write-Host $result
$Jobs.Remove($job)
return
}
}
}
while ($true) {
While ($script:currentjobs -le $maxjobs){
QueueJob $jobindex
$jobindex++
}
ServiceJobs
Start-Sleep 1
}
$runspace.Close()
[gc]::Collect()
It is using an infinite loop without proper termination and is lacking any sort of error checking, but hopefully it is enough to demonstrate the technique.