how do i get a variable out of powershell in jenkins declarative pipeline? - powershell

steps {
script{
env.StorysTested = ''
try{
powershell('''
//some code here
foreach ( $item in $Comments )
{
//some code here
//assigning a new value to StoryTested env variable
$env:StorysTested = "some value"
}
//below line works fine and displays the value
Write-Output "Stories tested : $env:StorysTested"
''')
//below null value is displayed for StorysTested``
echo " From Grrovy : ${env.StorysTested}"
}
catch(err)
{
throw err
}
}
I am using a jenkins declarative pipeline.
In the above code i m trying to use the value of $env:StorysTested in groovy which was assigned in powershell. Is there any way i can retain a variable value that was assigned in powershell, after the powershell execution is over. storing it in env variable was one way i thought of but clearly that didnt work.

If you set an environment variable using $env:StorysTested = "some value", this variable is stored for the powershell process and is not permanent or visible outside this process.
To create more permanent environment variables (i.e., user-level or machine-level) you need to use the .NET Framework and the SetEnvironmentVariable method:
[Environment]::SetEnvironmentVariable("StorysTested", "some value", "User")
or
[Environment]::SetEnvironmentVariable("StorysTested", "some value", "Machine")
To delete from within PowerShell, you use the same .NET method and assign a $null value to the variable like this:
[Environment]::SetEnvironmentVariable("StorysTested",$null,"User") # or "Machine" of course
Hope that helps

Related

Assign value to powershell variable within Jenkins pipeline

I came across several documents on how to run a Powershell script within Jenkins pipeline and how to capture output.
However I want to use the captured output for next node powershell script.
e.g.
node {
def msg = powershell(returnStdout: true, script: 'Write-Output "PowerShell is mighty!"')
}
Now I want to use msg in the next node within Powershell script. Like if we can assign it to a powershell variable and then perform operations with that variable.
Any pointers on how this can be achieved?
You can assign the powershell output to an environment variable and use that in the subsequent nodes:
node {
env.msg = powershell(returnStdout: true, script: 'Write-Output "PowerShell is mighty!"')
}
node {
def output = powershell(returnStdout: true, script: '''
$message = ($env:msg).trim()
Write-Output $message
''')
println(output)
}
Notice the env prefix before variable msg. You can retrieve the variable within powershell in next node using $env: prefix followed by variable name. Don't forget to trim the variable (.trim()) within powershell to remove newline.

How do I read values of a variable of type Map passed from terraform to powershell userdata script?

I need to pass the variable of type map from terraform to powershell userdata script and be able to access the key value pairs of the map in the powershell script. Thank you
userdata.tf
data "template_file" "user_data" {
template = "${file("${path.module}/init.ps1")}"
vars = {
environment = var.env
# I want to pass the values as shown below
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
}
}
init.ps1
$hostnames = "${hostnames}"
$environment = "${environment}"
if ($environment -eq "dev"){
# print the value of the dev key in the hostname map here
}
The template_file data source is discouraged.
Note In Terraform 0.12 and later, the templatefile function offers a built-in mechanism for rendering a template from a file. Use that function instead, unless you are using Terraform 0.11 or earlier.
The templatefile function is preferred which is why my solution uses it instead.
In either case, only map(string) is supported for template vars. The values must be strings. JSON can encode arbitrary tree structures, including your map of hostnames as strings.
In your terraform code, encode your hostnames to JSON with jsonencode.
userdata.tf:
locals {
user_data = templatefile("${path.module}/init.ps1" ,{
environment = var.env
# I want to pass the values as shown below
hostnames = jsonencode({"dev":"devhost","test":"testhost","prod":"prodhost"})
})
}
In your PowerShell, decode your hostnames from JSON with the ConvertFrom-Json cmdlet.
init.ps1:
$hostnames = '${hostnames}' | ConvertFrom-Json
$environment = "${environment}"
if ($environment -eq "dev"){
# print the value of the dev key in the hostname map here
}
Update: As noted in the comments, -AsHashtable won't necessarily work as it was added in PowerShell 6.0. Windows 10 and Windows Server 2016 include PowerShell 5.1. If you have maps with case-only differences in keys ({"name" = "foo" ; "Name" = "bar"}) then you will need to install PowerShell 6.0 or later and use ConvertFrom-Json -AsHashtable.
In order to include a collection value in a template result you must decide how you want to represent it as a string, because template results are always strings.
PowerShell supports JSON encoding via the ConvertFrom-Json cmdlet, so a JSON string might be a good candidate, although it presents some challenges because you must ensure that the JSON string is written into the result as a valid PowerShell expression, which means we must also apply PowerShell escaping.
Putting that all together, you can adjust the template like this:
$hostnames = '${replace(jsonencode(hostnames), "'", "''")}' | ConvertFrom-Json
$environment = '${replace(environment, "'", "''")}'
if ($environment -eq "dev"){
Write-Output $hostnames["dev"]
}
The jsonencode function produces a JSON-encoded version of the given value. The above then passes that result to replace so that any ' characters in the result will be escaped as '', which then allows placing the entire result in single quotes ' to ensure valid PowerShell syntax.
The result of rendering the template would be something like this:
$hostnames = '{"dev":"devhost","test":"testhost","prod":"prodhost"}' | ConvertFrom-Json -AsHashtable
$environment = 'dev'
if ($environment -eq "dev"){
Write-Output $hostnames["dev"]
}
You seem to be using Terraform 0.12, so you should use the templatefile function instead of the template_file data source. The function is better because it can accept values of any type, whereas the data source can only accept string values (because it is designed for Terraform 0.11).
To use templatefile, find the place where you were previously referring to data.template_file.user_data and use the templatefile function there instead:
templatefile("${path.module}/init.ps1", {
environment = var.env
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
})
You can then remove the data "template_file" "user_data" block, because this templatefile function call replaces it.

Explicit Return in Powershell

I can write the following code in javascript:
function sum(num1, num2) {
return num1 + num2;
}
and then get a value
var someNum = sum(2,5);
I would like to do the same thing in Powershell, but I read the following guide:
PowerShell also knows the return keyword; however, it follows a
different logic. In general, the purpose of return is to end the
execution of a code section and to give the control back to the parent
block.
If you add a parameter to the return statement, the value will indeed
be returned to the calling subroutine. However, this also applies for
all other statements with an output. This means that any output
produced in the function will be stored in the variable together with
the return parameter.
I want to do this for the sake of having pure functions. However, it seems doing
var someNum = sum(2,5);
is entirely redundant, when I can just call the function above, define someNum inside of it, and it will be available in the global scope.
Am I missing something or is it possible to write pure functions in Powershell that don't return everything inside the function?
A bit tangential, but here is my actual code:
function GetPreviousKeyMD5Hashes() {
$query = "SELECT Name, MD5, executed FROM [AMagicDb].[dbo].cr_Scripts";
$command = New-Object System.Data.SQLClient.SQLCommand;
$command.Connection = $connection;
$command.CommandText = $query;
try {
$reader = $command.ExecuteReader();
while ($reader.Read()) {
$key = $reader.GetString(1)
$previousScripts.Add($key) | Out-Null
}
$reader.Close();
Write-Output "$(Get-Date) Finished querying previous scripts"
}
catch {
$exceptionMessage = $_.Exception.Message;
Write-Output "$(Get-Date) Error running SQL at with exception $exceptionMessage"
}
}
and then:
$previousScripts = New-Object Collections.Generic.HashSet[string];
GetPreviousKeyMD5Hashes;
This code isn't clear to me at all - running GetPreviousKeyMD5Hashes does set $previousScripts, but this is entirely unclear to whoever modifies this after me. My only other alternative (afaik) is to have all this in line, which also isn't readable.
is entirely redundant, when I can just call the function above, define someNum inside of it, and it will be available in the global scope.
No: functions execute in a child scope (unless you dot-source them with .), so variables created or assigned to inside a function are local to it.
Am I missing something or is it possible to write pure functions in Powershell that don't return everything inside the function?
Yes: The implicit output behavior only applies to statements whose output is neither captured - $var = ... - nor redirected - ... > foo.txt
If there are statements that happen to produce output that you'd like to discard, use $null = ... or ... > $null
Note: ... | Out-Null works in principle too, but will generally perform worse, especially in earlier PowerShell versions - thanks, TheIncorrigible1.
If there are status messages that you'd like to write without their becoming part of the output, use Write-Host or, preferably Write-Verbose or, in PSv5+, Write-Information, though note that the latter two require opt-in for their output to be visible in the console.
Do NOT use Write-Output to write status messages, as it writes to the success output stream, whose purpose is to output data ("return values").
See this answer of mine for more information about PowerShell's output streams.
The equivalent of your JavaScript code is therefore:
function sum($num1, $num2) {
Write-Host "Adding $num1 and $num2..." # print status message to host (console)
$num1 + $num2 # perform the addition and implicitly output result
}
PS> $someNum = sum 1 2 # NOTE: arguments are whitespace-separated, without (...)
Adding 1 and 2... # Write-Host output was passed through to console
PS> $someNum # $someNum captured the success output stream of sum()
3
Am I missing something or is it possible to write pure functions in Powershell that don't return everything inside the function?
You can't have your cake and eat it too...
If you have no out put in your function, then it is "pure" like you desire. If you have output, that also becomes part of the return.
You can use [ref] params. See below for example.
function DoStuff([ref]$refObj)
{
Write-Output "DoStuff: Enter"
$refObj.Value += $(1 + 2)
$refObj.Value += "more strings"
Write-Output "DoStuff: Exit"
}
$refRet = #()
$allRet = DoStuff([ref]$refRet)
"allRet"
$allRet
"refRet"
$refRet
"`n`nagain"
$allRet = DoStuff([ref]$refRet)
"allRet"
$allRet
"refRet"
$refRet
Note: Powershell doesn't need semicolons at the end of each statement; only for separating multiple statements on the same line.
Whenever possible, it's a good idea to avoid changing global state within a function. Pass input as parameters, and return the output, so you aren't tied to using the function in only one way. Your sample could look like this:
function sum
{
param($num1,$num2)
return $num1+$num2
}
$somenum=sum 2 5
Now, with Powershell, the return statement isn't needed. The result of every statement that isn't otherwise assigned, captured, redirected, or otherwise used, is just thrown in with the return value. So we could replace the return statement above with simply
$num1+$num2
You're already making use of this in your code with:
$previousScripts.Add($key) | Out-Null
where you are discarding the result of .Add(). Otherwise it would be included in the return value.
Personally, I find using return to explicitly mark the return value makes it easier to read. Powershell's way of putting all if the output in the return caused a lot of trouble for me as I was learning.
So, the only fixes to your code I would make are:
Move $previousScripts = New-Object Collections.Generic.HashSet[string] to inside the function, making it local.
Add return $previousScripts to the end of the function.

Powershell - persist variable outside of function

I'm trying to import an xml file and store as a variable for the remainder of a powershell session. The import is obviously successful but the variable content does not persist outside of the function.
Function auth
{
$cred = import-clixml -Path c:\temp\cred.xml
}
try this:
Function auth
{
$global:cred = "test"
}
auth
$global:cred
You can use globals as Esperento57 suggests or you can do this
function auth
{
return 'test'
}
$cred = auth
More succinct:
function auth
{
'test'
}
$cred = auth
You need to declare the variable outside the scope of the function first and then inside the function explicitly tell the variable to update using the script:var method.
Here's the example is taken from https://www.kongsli.net/2013/04/25/powershell-gotchas-refer-to-variables-outside-a-function/ to which credit is given.
The thing is that we have to explicitly tell Powershell to update the variable in the parent scope instead of creating a new variable in the current scope.
$x = 1
function changeit {
"Changing `$x. Was: $x"
$script:x = 2
"New value: $x"
}
"`$x has value $x"
changeit
"`$x has value $x"
If you need to do this but with a number of functions and variables, you can place them all into a script and then dotsource the script.
Imagine a script like this:
#MyDevFunctions.ps1
$myImportantVar = "somevar"
$myOtherVar = "ABC123"
Function Get-MyCoolValue(){$myImportantVar}
Write-Host "Finished Loading MyDevFunctions"
If you wanted to run this, and then also persist the values of the variables and also the functions themselves, from your parent script you simply invoke it like so:
PS > . .\MyDevFunctions.ps1
"Finished Loading MyDevFunctions"
PS > $myOtherVar
ABC123
PS> Get-MyCoolValue
someVar

Powershell initializing global to $null is failing

I am running a GUI that takes user input but can be changed before finalizing if they make a mistake. When the form opens, I am trying to initialize the globals to null. This is failing. I put a breakpoint on the code and looked at the value before and then stepped into it. The value does not change.
So for example, if I run the form and enter "Foo" as my global variable, exit the form, then run the form again, even after the line in question executes, the value of the global is still "Foo". What is going on? I have used this exact code with other GUIs and it never failed (but the values were generated automatically rather than based on user input).
# Define and initialize global variables
$global:ServerName = $null # <-- This fails to reset the variable from the previous run of the form
function ValidateChoices(){
$OKToGo = $true
$TempServerName = $null
try {
# Only Allow Valid NETBios Name with AlphaNumberic and - up to 15 characters
[ValidatePattern("^[A-Za-z\d-]{1,15}$")]$TempServerName = $ServerNameTextbox.Text
$ServerNameTitle.BackColor = ""
$ServerNameTextbox.BackColor = ""
$global:ServerName = $TempServerName
} catch {
$OKToGo = $false
$ServerNameTitle.BackColor = "Pink"
$ServerNameTextbox.BackColor = "Pink"
}
...
if ( $OKToGo ){
"ServerName=" + $global:ServerName | Out-File c:\debug.txt
}
}
Here is the answer: When ValidatePattern is run against a variable, those restrictions are kept and re-evaluated anytime an attempt to change the variable is made. This holds true even if ValidatePattern was not explicitly called. And because it was a global variable, those restrictions rode through multiple iterations of the form. Because $null does not conform to my listed ValidatePattern parameters, the call to change the value was ignored