Run powershell commands atomically - powershell

I have 3 powershell commands that need to run synchronously, as I need to pass the output from the 1st to the 2nd, and so on. However if any fail I need to back out of all 3. Is there a cmdlet or technique, even, that will enable this behavior in powershell?

You could wrap each of your commands in an object or a hashtable with a corresponding "rollback" action and a test:
$Commands = #(
#{
Action = { Set-Value $PathOne "newValue" }
RollBack = { Set-Value $PathOne "oldValue" }
Test = { Test-Something }
}
#{
Action = { Set-Value $PathTwo "newValue" }
RollBack = { Set-Value $PathTwo "oldValue" }
Test = { Test-Something }
}
)
Then use a Stack<Scriptblock> to keep track of actions that need to be executed in the event of a "roll-back"
$RollbackStack = [System.Collections.Generic.Stack[Scriptblock]]::new()
foreach($Command in $Commands){
# Execute command
& $Command.Action
# Add rollback action to stack
$RollbackStack.Push($Command.Rollback)
# Test result, roll back if failed
if(-not(& $Command.Test)){
foreach($rollbackAction in $RollbackStack){
& $rollbackAction
}
}
}

In general, PowerShell commands do not support "backing out" or "rollback". The Registry provider does support transactions (which would allow this kind of behavior), but only with operations involving the registry.
It might be possible to capture the state of things that you're updating in the commands and re-applying those states, but it would be a very manual process.

Related

PowerShell: How to clear cache of included files?

I include an external .ps1 into antother .ps1:
foo.ps1:
.("C:\test\bar.ps1");
$obj = [bar]::new();
$obj.out();
bar.ps1:
class bar{
$output;
bar(){
$this.output = 1;
}
[void] out(){
write-host $this.output;
}
}
The first time I execute foo.ps1 in the Windows PowerShell ISE the output is "1", as expected.
Then I go to bar.ps1 and change $this.output = 1; to $this.output = 2;. After executing foo.ps1 again the output is still "1". When I change something in foo.ps1, like simply appending a new line, and execute it once again, the output becomes "2". Changing back, like removing the new line, will make an output of "1" again.
For me it looks like an caching issue. Is it possible to clear or disable the caching?
Thanks in advance!

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

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

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.

How to check to see if what went through the hashtable matched anything

I have a hashtable and I'm trying to make an if statement right now that will check to see if what went through the hashtable matched anything within it.
$netVerConv = #{
'v2.0' = "lib\net20";
'v3.0' = "lib\net30";
'v3.5' = "lib\net35";
'v4.0' = "lib\net40";
'v4.5' = "lib\net45";
}
$target = $netVerConv.Get_Item($netVerShort)
if () {
}
Above is the area of my code I'm working with, the target variable runs $netVerShort through the $netVerConv hashtable using a Get_Item command. The if statement that I've laid the framework for would check to see if netVerShort matched anything within the hashtable and if it didn't it will stop the program, which I know how to do with a simple exit command.
The other suggestions will work in your specific scenario but in general you should use the ContainsKey() method to see if a key exists in the hashtable. For instance the hashtable value could be $null or $false in which case, testing via the result of Get_Item() or more simply Item[$netVerShort], will return a false negative. So I recommend this approach for testing existence of a key in a hashtable. It is also more obvious what your intent is:
if (!$netVerConv.ContainsKey($netVerShort) {
...
}
How about this:
if( $target -eq $null ) {
echo "Didn't Match"
exit
}
Another option:
if (-not ($target = $netVerConv.Get_Item($netVerShort)))
{
Write-Error "Version $netVerShort not found"
Exit
}
You could also re-factor that as a Switch
$target =
Switch ($netVerShort)
{
'v2.0' {"lib\net20"}
'v3.0' {"lib\net30"}
'v3.5' {"lib\net35"}
'v4.0' {"lib\net40"}
'v4.5' {"lib\net45"}
Default {
Write-Error "Version $netVerShort not found"
Exit
}
}

Perl reset variable

I'm learning Perl, and I made a script that will use defined variables in the beginning of my script to establish connection,pull records, modify them, then close connection.
the second part of my work involve repeating the same steps but for different server.
is there is a way to un-set whatever variables has been set before? and then use new defined settings and repeat the steps?
Thank you
Define your variables in their own scope.
{
my $server = '123.123.123.123';
my $username = 'user1';
ping($server);
login($username);
}
{
my $server = '222.222.123.123';
my $username = 'user2';
ping($server);
login($username);
}
Even better, use a function definition:
sub doSomethingToServer
{
my ($server, $username) = #_;
ping($server);
login($username);
}
doSomethingToServer('123.123.123.123', 'user1');
doSomethingToServer('222.222.123.123', 'user2');