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

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

Related

PowerShell function to check the health of remote disks not actually remoting

I am trying to use a script to check the health of physical disks on Lenovo computers utilizing the storcli tool. I found this and have tried to modify it to be a function and allow the input of remote computers and eventually use Get-Content to input a server list. For whatever reason it takes the computer input from -ComputerName but does not actually run the commands on the remote computer. It seems to just read the disks on the local machine and always reports back "Healthy" while I know there are bad disks on the remote machine. Also I have run the script on the machine with the bad disks and it does work and reports the disk as failed. Could anyone offer any insight into what I am missing for this to actually check the remote machines? Remoting is enabled as I can run other scripts without issue. Thank you in advance.
Function Get-DriveStatus {
[cmdletbinding()]
param(
[string[]]$computername = $env:computername,
[string]$StorCLILocation = "C:\LenovoToolkit\StorCli64.exe",
[string]$StorCliCommand = "/c0/eall/sall show j"
)
foreach ($computer in $computername) {
try {
$ExecuteStoreCLI = & $StorCliLocation $StorCliCommand | out-string
$ArrayStorCLI= ConvertFrom-Json $ExecuteStoreCLI
}catch{
$ScriptError = "StorCli Command has Failed: $($_.Exception.Message)"
exit
}
foreach($PhysicalDrive in $ArrayStorCLI.Controllers.'Response Data'.'Drive Information'){
if(($($PhysicalDrive.State) -ne "Onln") -and ($($PhysicalDrive.State -ne "GHS"))) {
$RAIDStatus += "Physical Drive $($PhysicalDrive.'DID') With Size $($PhysicalDrive.'Size') is $($PhysicalDrive.State)`n"
}
}
#If the variables are not set, We’re setting them to a “Healthy” state as our final action.
if (!$RAIDStatus) { $RAIDStatus = "Healthy" }
if (!$ScriptError) { $ScriptError = "Healthy" }
if ($ScriptError -eq "Healthy")
{
Write-Host $computer $RAIDStatus
}
else
{
Write-Host $computer "Error: ".$ScriptError
}
}#End foreach $computer
}#End function
$RAIDStatus = $null
$ScriptError = $null

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

Converting .ps1 file to a Windows Service

I'm trying to convert a .ps1 file to run as a windows service. This needs to run as a service as it's requirements for Business Continuity (scheduled task is not an option). i've always used NSSM to wrap the .ps1 as it will then run via NSSM as an exe.
This works for different scripts in Windows Server 2012, but this script is slightly different and i'm required to get this service to work on Windows Server 2016. The script itself, connects to a large amount of other servers (in total i'll have 3 services - Windows Service / Windows Process / Linux Process) which all work when just running within PowerShell.
Below is an example of the start of the script so you get an idea how it works (may not be relevant);
while ($test = 1)
{
[string]$query
[string]$dbServer = "DBSERVER" # DB Server (either IP or hostname)
[string]$dbName = "DBNAME" # Name of the database
[string]$dbUser = "CONNECTIONUSER" # User we'll use to connect to the database/server
[string]$dbPass = "CONNECTIONPASSWORD" # Password for the $dbUser
$conn = New-Object System.Data.Odbc.OdbcConnection
$conn.ConnectionString = "Driver={PostgreSQL Unicode(x64)};Server=$dbServer;Port=5432;Database=$dbName;Uid=$dbUser;Pwd=$dbPass;"
$conn.open()
$cmd = New-object System.Data.Odbc.OdbcCommand("select * from DBNAME.TABLENAME where typeofcheck = 'Windows Service' and active = 'Yes'",$conn)
$ds = New-Object system.Data.DataSet
(New-Object system.Data.odbc.odbcDataAdapter($cmd)).fill($ds) | out-null
$conn.close()
$results = $ds.Tables[0]
$Output = #()
foreach ($result in $results)
{
$Hostone = $Result.hostone
$Hosttwo = $Result.hosttwo
$Hostthree = $Result.hostthree
$Hostfour = $Result.hostfour
Write-Output "checking DB ID $($result.id)"
#Host One Check
if (!$result.hostone)
{
$hostonestatus = 17
$result.hostone = ""
}
else
{
try
{
if(Test-Connection -ComputerName $result.hostone -quiet -count 1)
{
$hostoneres = GET-SERVICE -COMPUTERNAME $result.hostone -NAME $result.ServiceName -ErrorAction Stop
$hostonestatus = $hostoneres.status.value__
$Result.HostOneCheckTime = "Last checked from $env:COMPUTERNAME at $(Get-date)"
}
else
{
$hostonestatus = 0
$result.hostonestatus = "Failed"
$Result.HostOneCheckTime = "Last checked from $env:COMPUTERNAME at $(Get-date)"
}
}
catch
{
$hostonestatus = 0
$result.hostonestatus = "Failed"
$Result.HostOneCheckTime = "Last checked from $env:COMPUTERNAME at $(Get-date) Errors Found"
}
if ($hostonestatus -eq 4)
{
$result.hostonestatus = "Running"
}
if ($hostonestatus -eq 1)
{
$result.hostonestatus = "Stopped"
}
elseif ($hostonestatus -eq 0)
{
$result.hostonestatus = "Failed"
}
}
As mentioned, the exact script running standalone works seamlessly.
Whats the best way to run this as a service or are there any known issues with NSSM when using it with Windows Server 2016?
I've also found the below which may be pointing in the right direction as i've intermittently got these in the logs;
DCOM event ID 10016 is logged in Windows
Windows sysadmin here.
Quiet a few different ways to accomplish this from a purely service-orientated perspective.
--- 1 ---
If you are using Server 2016, I believe that the Powershell command 'New-Service' may be one of the cleanest ways. Have a look at the following for syntax and if it suits your use case --
https://support.microsoft.com/en-au/help/137890/how-to-create-a-user-defined-service
This CMDlet takes a credential parameter, so depending on your use case may be good to access resources on other foreign machines.
--- 2 ---
Another way is to use the old trusty in-built SC.exe utility in windows.
SC CREATE <servicename> Displayname= "<servicename>" binpath= "srvstart.exe <servicename> -c <path to srvstart config file>" start= <starttype>
An example --
SC CREATE Plex Displayname= "Plex" binpath= "srvstart.exe Plex -c C:PlexService.ini" start= auto
As far as I can tell, this will create a service that will execute under the Local System context. For more information, have a look at the following:
https://support.microsoft.com/en-au/help/251192/how-to-create-a-windows-service-by-using-sc-exe
https://www.howtogeek.com/50786/using-srvstart-to-run-any-application-as-a-windows-service/
--- 3 ---
You may want to consider manually injecting some registry keys to create your own service (which is essentially what SC.exe does under the hood).
Although I'm unfortunately in no position at the moment to provide boiler-plate code, I'd encourage that you have a look at the following resource:
https://www.osronline.com/article.cfm%5Eid=170.htm
NOTE - you will need to provide all required sub-keys for it to work.
As with any registry changes, please make a backup of your registry and perform edits at your own risk prior to making any changes. I can only recommend to try this on a spare/test VM if possible prior to implementing to prod.

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

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?

Powershell Script: prompt a file (by mask) and use that file in a command line

Disclaimer: I don't know enough about ps to accomplish this in a reasonable amount of time, so yes, I am asking someone else to do my dirty job.
I want to be able to run a web.config transformation without opening a command line.
I have following files in a folder:
web.config - actual web config
web.qa.config - web config transformation for qa env
web.production.config - web config transformation for production env
transform.ps1 - powershell script I want to use to run transformation
Here is what I want:
PS file shall enumerate current directory using .*\.(?<env>.*?)\.config and let me choose which <env> I am interested in generate web.config for. In my example I will be presented with two options: "qa", "production".
After I (user) select the environment (let's say it is "qa", selected environment is stored as $env, and corresponding filename will be stored as $transformation) script shall do following:
backup original web.config as web.config.bak
execute following command:
.
echo applying $transformation...
[ctt][1].exe source:web.config transformation:$transformation destination:web.config preservewhitespaces verbose
echo done.
ctt.exe is a tool based on XDT that runs web.config transformation from command line.
Okay, looks simple enough, I'll do your dirty job for you. ;)
Save the following as transform.ps1:
$environments = #()f
gci | %{if ($_ -match '.*\.(?<env>.*?)\.config') {$environments += $matches.env}}
Write-Host "`nEnvironments:"
for ($i = 0; $i -lt $environments.Length; $i++) {Write-Host "[$($i + 1)] $($environments[$i])"}
Write-Host
do {
$selection = [int](Read-Host "Please select an environment")
if ($selection -gt 0 -and $selection -le $environments.Length) {
$continue = $true
} else {
Write-Host "Invalid selection. Please enter the number of the environment you would like to select from the list."
}
} until ($continue)
$transformation = "web.$($environments[$selection - 1]).config"
if (Test-Path .\web.config) {
cpi .\web.config .\web.config.bak
} else {
Write-Warning "web.config does not exist. No backup will be created."
}
if ($?) {
Write-Host "`nApplying $transformation..."
[ctt][1].exe source:web.config transformation:$transformation destination:web.config preservewhitespaces verbose
Write-Host "Done.`n"
} else {
Write-Error "Failed to create a backup of web.config. Transformation aborted."
}