How can I set a variable in a conditional statement? - powershell

I have a script that checks the health of a pc by parsing through log files looking for indicators of compromise. If the script finds a certain event id it returns a normalized message. The end goal is to do math on these returns- this will generate a health score for that PC.
What I need to know is how set a variable (say X with a value of 1) if the event id is found, and set the same variable (say X with a value of 2) if the event id is not found. If I just set both variables in the script -in their respective if/else blocks, won't the last variable always overwrite the first regardless of the condition?

Unfortunately PowerShell doesn't have a conditional assignment statement like Perl ($var = (<condition>) ? 1 : 2;), but you can assign the output of an if statement to a variable:
$var = if (<condition>) { 1 } else { 2 }
Of course you could also do the "classic" approach and assign the variable directly in the respective branches:
if (<condition>) {
$var = 1
} else {
$var = 2
}
The second assignment doesn't supersede the first one, because only one of them is actually executed, depending on the result of the condition.
Another option (with a little more hack value) would be to calculate the values from the boolean result of the condition. Negate the boolean value, cast it to an int and add 1 to it:
$var = [int](-not (<condition>)) + 1

In Powershell 7 you can use the ternary operator:
$x = $true ? 1 : 2
echo $x
displays 1.
What you may want however is switch, e.g.,
$in = 'test2'
$x = switch ($in) {
'test1' {1}
'test2' {2}
'test3' {4}
}
echo $x
displays 2.

A little example that can help to understand.
PowerShell script:
$MyNbr = 10
$MyMessage = "Crucial information: " + $(
if ($MyNbr -gt 10) {
"My number is greater than 10"
} elseif ($MyNbr -lt 10) {
"My number is lower than 10"
} else {
"My number is 10"
}
)
Write-Host $MyMessage
Output:
Crucial information: My number is 10
If you change the MyNbr variable, you will have a different result depending on conditions in the if statements.

You actually can evaluate a condition and assign the result to a variable. It's the closest you get to a ternary operator in PowerShell. I find it makes it simpler to do complicated conditional statements. It makes it easier to read and change them. Here's an example:
$theEventIdIWant = 6000
$eventId = 5000
$correct = $eventID -eq $theEventIdIWant
if($correct) {$true} else {$false}

Related

Is it possible to use a variable to store input value(s) and then use the variable to invoke hash table value(s) using PowerShell?

I'm not sure how to explain what I'm trying to do but maybe the code will help you understand.
I might get a few terms wrong, I'm still learning.
I was not unable to find any documentation that would help my case.
I feel like I'm complicating things and I could just use a lot of "if" statements but this seemed to be a way more efficient way (if it's possible) for what I'm trying to accomplish.
I am trying to invoke multiple Hash values with the "foreach" statement using the "read-host" inputs.
#Hash
$AppGroupsHash = #{
1 = "AppADGroup1"
2 = "AppADGroup2"
3 = "AppADGroup3"
4 = "AppADGroup4"
}
$AppGroups = $AppGroupsHash.Values -as [string[]]
#write-host for flair and choices to choose from.
Write-Host "`n1 = AppADGroup1`n2 = AppADGroup1`n3 = AppADGroup1`n4 = AppADGroup1`n " -ForegroundColor Yellow
#User input value(s) for the required task
#Not sure if [string] is necessary in front of $AppGroupsKeys
[string]$AppGroupsKeys = Read-Host "Enter App Hash values (1 - 2 - 3 - 4) (No space, delimit with ';') "
foreach ($AppGroupsKey in $AppGroupsKeys.split(';'))
{
if ($AppGroupsHash.Keys -ccontains $AppGroupsKey)
{
#To test if it will print the values I'm trying to invoke
echo $AppGroupsKey
#This is where I'm getting into trouble
$AppGroupsHash.($AppGroupsKey)
}
}
When I run it. This is what I get, Read-Host Values: 1;3;4 :
#These are the values being echoed but the "$AppGroupsHash.($AppGroupsKey)" does not work
1
3
4
What it should print out:
#Test "echo values"
1
3
4
#The "values" I want
AppADGroup1
AppADGroup3
AppADGroup4
I want to use the variables I've inputted as the Keys that are stored in the hash so that I can invoke those values, these will be eventually used to add users to those groups based on their requirements.
This is what I tried:
foreach ($AppGroupsKey in $AppGroupsKeys.split(';'))
{
if ($AppGroupsHash.Keys -ccontains $AppGroupsKey)
{
#To test if it will print the values I'm trying to invoke
echo $AppGroupsKey
#This is where I'm getting into trouble
write-host "$AppGroupsHash.$AppGroupsKey;"
}
}
Output:
1
System.Collections.Hashtable.1;
2
System.Collections.Hashtable.2;
4
System.Collections.Hashtable.4;
I feel like it's possible but I also feel like I'm missing some sort of secret syntax for this to work.
Is it possible what I want to accomplish or should I try Arrays or just a bunch of "if" statements?

Powershell For Loop In Reverse

I have the following code:
$testString = "abcdef"
for ($ia=$testString.length-1; $ia -gt 0; $ia - 2) {
$testString[$ia]
}
I need output "fedcba"
But keep getting "fffffffffff..."
How can I reverse the testString output by each character in this for loop format?
Another way with a reverse range in the array index. It should really start at length-1, but it's easier this way and it works. I wish I could do [-1..0] ([-1..-100]?).
$teststring = 'abcdef'
$teststring[$teststring.length..0]
f
e
d
c
b
a
-join $teststring[$teststring.length..0]
fedcba
-join $teststring[-1..-$teststring.length]
fedcba
Why are you using $ia - 2 ?
You need to change that to $ia-- so it decrements one at a time.
Also change $ia -gt 0 to $ia -ge 0 so it reaches the last index.
You almost got it.
$testString = "abcdef"
for ($ia=$testString.length-1; $ia -ge 0; $ia--) {
$testString[$ia]
}
You can use $ia-- to decrement down and you should get your result. Also, you should use -ge for greater than or equal to 0 as you want to capture index 0 of the array.
Also, vice versa, you can do the following:
$testString = "abcdef"
for ($ia=$testString.length; $ia -gt -1; $ia--) {
$testString[$ia]
}
Both works, but I prefer the second one as it's easier to read.
Just to make you aware that there is also a static dotnet Reverse method from the [Array] class:
$CharArray = $teststring.ToCharArray()
[Array]::Reverse($CharArray)
$CharArray
f
e
d
c
b
a
Related GitHub purpose: #16644 Add -Reverse parameter to Sort-Object

finding index of key in an ordered dictionary in powershell

I am having a little bit of trouble with hashtables/dictionaries in powershell. The most recent roadblock is the ability to find the index of a key in an ordered dictionary.
I am looking for a solution that isn't simply iterating through the object.
(I already know how to do that)
Consider the following example:
$dictionary = [Ordered]#{
'a' = 'blue';
'b'='green';
'c'='red'
}
If this were a normal array I'd be able to look up the index of an entry by using IndexOf().
[array]::IndexOf($dictionary,'c').
That would return 2 under normal circumstances.
If I try that with an ordered dictionary, though, I get -1.
Any solutions?
Edit:
In case anyone reading over this is wondering what I'm talking about. What I was trying to use this for was to create an object to normalize property entries in a way that also has a numerical order.
I was trying to use this for the status of a process, for example:
$_processState = [Ordered]#{
'error' = 'error'
'none' = 'none'
'started' = 'started'
'paused' = 'paused'
'cleanup' = 'cleanup'
'complete' = 'complete'
}
If you were able to easily do this, the above object would give $_processState.error an index value of 0 and ascend through each entry, finally giving $_processState.complete an index value of 5. Then if you compared two properties, by "index value", you could see which one is further along by simple operators. For instance:
$thisObject.Status = $_processState.complete
If ($thisObject.Status -ge $_processState.cleanup) {Write-Host 'All done!'}
PS > All done!
^^that doesn't work as is, but that's the idea. It's what I was aiming for. Or maybe to find something like $_processState.complete.IndexNumber()
Having an object like this also lets you assign values by the index name, itself, while standardizing the options...
$thisObject.Status = $_processState.paused
$thisObject.Status
PS > paused
Not really sure this was the best approach at the time or if it still is the best approach with all the custom class options there are available in PS v5.
It can be simpler
It may not be any more efficient than the answer from Frode F., but perhaps more concise (inline) would be simply putting the hash table's keys collection in a sub expression ($()) then calling indexOf on the result.
For your hash table...
Your particular expression would be simply:
$($dictionary.keys).indexOf('c')
...which gives the value 2 as you expected. This also works just as well on a regular hashtable... unless the hashtable is modified in pretty much any way, of course... so it's probably not very useful in that case.
In other words
Using this hash table (which also shows many of the ways to encode 4...):
$hashtable = [ordered]#{
sample = 'hash table'
0 = 'hello'
1 = 'goodbye'
[char]'4' = 'the ansi character 4 (code 52)'
[char]4 = 'the ansi character code 4'
[int]4 = 'the integer 4'
'4' = 'a string containing only the character 4'
5 = "nothing of importance"
}
would yield the following expression/results pairs:
# Expression Result
#------------------------------------- -------------
$($hashtable.keys).indexof('5') -1
$($hashtable.keys).indexof(5) 7
$($hashtable.keys).indexof('4') 6
$($hashtable.keys).indexof([char]4) 4
$($hashtable.keys).indexof([int]4) 5
$($hashtable.keys).indexof([char]'4') 3
$($hashtable.keys).indexof([int][char]'4') -1
$($hashtable.keys).indexof('sample') 0
by the way:
[int][char]'4' equals [int]52
[char]'4' has a "value" (magnitude?) of 52, but is a character, so it's used as such
...gotta love the typing system, which, while flexible, can get really really bad at times, if you're not careful.
Dictionaries uses keys and not indexes. OrderedDictionary combines a hashtable and ArrayList to give you order/index-support in a dictionary, however it's still a dictionary (key-based) collection.
If you need to get the index of an object in a OrderedDictionary (or a hasthable) you need to use foreach-loop and a counter. Example (should be created as a function):
$hashTable = [Ordered]#{
'a' = 'blue';
'b'='green';
'c'='red'
}
$i = 0
foreach($key in $hashTable.Keys) {
if($key -eq "c") { $i; break }
else { $i++ }
}
That's how it works internaly too. You can verify this by reading the source code for OrderedDictionary's IndexOfKey method in .NET Reference Source
For the initial problem I was attempting to solve, a comparable process state, you can now use Enumerations starting with PowerShell v5.
You use the Enum keyword, set the Enumerators by name, and give them an integer value. The value can be anything, but I'm using ascending values starting with 0 in this example:
Enum _ProcessState{
Error = 0
None = 1
Started = 2
Paused = 3
Cleanup = 4
Complete = 5
Verified = 6
}
#the leading _ for the Enum is just cosmetic & not required
Once you've created the Enum, you can assign it to variables. The contents of the variable will return the text name of the Enum, and you can compare them as if they were integers.
$Item1_State = [_ProcessState]::Started
$Item2_State = [_ProcessState]::Cleanup
#return state of second variable
$Item2_state
#comparison
$Item1_State -gt $Item2_State
Will return:
Cleanup
False
If you wanted to compare and return the highest:
#sort the two objects, then return the first result (should return the item with the largest enum int)
$results = ($Item1_State,$Item2_State | Sort-Object -Descending)
$results[0]
Fun fact, you can also use arithmetic on them, for example:
$Item1_State + 1
$Item1_State + $Item2_State
Will return:
Paused
Verified
More info on Enum here:
https://blogs.technet.microsoft.com/heyscriptingguy/2015/08/26/new-powershell-5-feature-enumerations/
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_enum?view=powershell-6
https://psdevopsug.scot/post/working-with-enums-in-powershell/

How to return one and only one value from a PowerShell function?

I've learned from this Stack Overflow question, that PowerShell return semantics are different, let's say, from C#'s return semantics. Quote from the aforementioned question:
PowerShell has really wacky return semantics - at least when viewed from a more traditional programming perspective. There are two main ideas to wrap your head around: All output is captured, and returned.
The return keyword really just indicates a logical exit point.
Let's look at this example:
function Calculate
{
echo "Calculate"
return 11
}
$result = Calculate
If you echo $result you will realise that something is not right. You'd expect this:
11
But the reality, what you actually see, is different:
Calculate
11
So instead of getting back only the intended return value, you actually get back an array.
You could get rid of the echo statements and not polluting the return value, but if you call another function from your function, which echoes something, then you're in trouble again.
How can I write a PowerShell function that only returns one thing? If the built-in PowerShell functions do return only one value, why can't I do that?
The workaround that I'm using now and I'd love to get rid of:
function Calculate
{
# Every function that returns has to echo something
echo ""
return 11
}
# The return values is the last value from the returning array
$result = (Calculate)[-1]
You can also just assign everything to null, and then return just the variable you want:
Function TestReturn {
$Null = #(
Write-Output "Hi there!"
1 + 1
$host
$ReturnVar = 5
)
return $ReturnVar
}
Several answers already given do not fully answer your question because they do not account for other cmdlets you might call--as you point out--that might "pollute" your output. The answer from #Laezylion is essentially correct but I believe bears further elaboration.
It is clear that you appreciate that your current workaround--forcing every function to return an array then taking the last element--is not a very robust solution. (In fact, I think it is safe to call that a kludge. :-) The correct approach--and a direct answer to your question--is clear... though at first glance it sounds like a tautology:
Question: How do you ensure a function returns only one thing?
Answer: By ensuring that it returns only one thing.
Let me elucidate. There are really two kinds of functions/cmdlets in PowerShell: (A) those that return data and (B) those that do not return data but may instead report progress or diagnostic messages. Problems arise, as you have seen, when you try to mix the two. And this may easily happen inadvertently. Thus, it is your responsibility, as the function author, to understand each function/cmdlet call within your function: specifically, does it return anything, be it a true return value or just diagnostic output? You are expecting output from type A functions, but you need to be wary of any type B functions. For any one that does return something you must either assign it to a variable, feed it into a pipeline, or... make it go away. (There are several ways to make it go away: pipe it to Out-Null, cast it to void, redirect it to $null, or assign it to $null. See Jason Archer’s Stack Overflow post that evaluates the performance of each of these flavors, suggesting you shy away from Out-Null.)
Yes, this approach takes more effort, but you get a more robust solution by doing so. For a bit more discussion on this very issue, these A/B functions, etc. see Question 1 on A Plethora of PowerShell Pitfalls recently published on Simple-Talk.com.
Don't use echo to write information, use Write-Host, Write-Verbose or Write-Debug depending on the message.
Echo is an alias for Write-Output which will send it as an Object through the pipeline. Write-Host/Verbose/Debug however is only console-messages.
PS P:\> alias echo
CommandType Name ModuleName
----------- ---- ----------
Alias echo -> Write-Output
Sample:
function test {
Write-Host calc
return 11
}
$t = test
"Objects returned: $($t.count)"
$t
#Output
calc
Objects returned: 1
11
Also, if you return the value on the last line in your function, then you don't need to use return. Writing the Object/value by itself is enough.
function test {
Write-Host calc
11
}
The difference is that return 11 would skip the lines after it you use it in the middle of a function.
function test {
return 11
12
}
test
#Output
11
Worth mentioning:
Any unmanaged command output inside your function will end up as a return value.
I use to get this trouble with a function managing XML content:
Function Add-Node(NodeName){
$NewXmlNode = $Xml.createElement("Item")
$Xml.Items.appendChild($NewXmlNode) | out-null
}
If you remove the Out-Null, the return of your function will be the Node basic properties... (You can also manage the exit code of the appendChild command ... it depends on the motivation) :)
#Michael Sorens's answer is useful, but it suggests a design strategy that seems rather onerous. I don't want to have to separate out data processing functions from functions that report progress or diagnostic messages. Reading his blogpost, I found a less hacky work-around to the OP's question. Here's the key sentence from the blog post:
A PowerShell function returns all uncaptured output.
So what if we capture our output?
function Calculate
{
$junkOutput = echo "Calculate"
return 11
}
$result = Calculate
Now $result just contains 11, as intended.
You could replace echo (alias of Write-Output) with Write-Host:
PS> function test{ Write-Host "foo";"bar"}
PS> $a = test
test
PS >$a
bar
echo is an alias for Write-Output which sends the object to the next command.
Using Write-Host should help:
PS:> function Calculate
>> {
>> # Every function that returns has to echo something
>> Write-Host "test"
>> return 11
>> }
>>
PS:> $A = Calculate
test
PS:> $A
11
The use of parenthesis seems critical when calling a function that accepts parameters.
This example fills a DataTable from SQL Server, and returns an [int] from the first row, fourth column.
Note the parenthesis around the actual function call assigned to the variable [int]$tt1
function InvokeSQL-GetOneCount { param( [string]$cn, [string]$prj )
$sql = "SELECT * FROM [dbo].[dummytable] WHERE ProjectNumber = '$prj' ORDER BY FormID, PartID;"
$handler = [System.Data.SqlClient.SqlInfoMessageEventHandler] { param($sender, $event) Handle-Message -message $event.Message -fcolor "yellow" -bcolor "black"; };
$conn = new-Object System.Data.SqlClient.SqlConnection($cn)
$conn.add_InfoMessage($handler);
$conn.FireInfoMessageEventOnUserErrors = $true;
$cmd = New-Object System.Data.SqlClient.SqlCommand;
$cmd.Connection = $conn;
$cmd.CommandType = [System.Data.CommandType]::Text;
$cmd.CommandText = $sql;
$cmd.CommandTimeout = 0;
$sa = New-Object System.Data.SqlClient.SqlDataAdapter($cmd);
$dt = New-Object System.Data.DataTable("AllCounts");
$sa.Fill($dt) | Out-Null;
[int]$rval = $dt.Rows[0][3];
$conn.Close();
return $rval;
}
clear-host;
[int]$tt1 = (InvokeSQL-GetOneCount -cn "Server=[your server];DataBase=[your catalog];Integrated Security=SSPI" -prj "MS1904");
write-host $tt1;
I ended up using the following to make sure only one value is returned:
write-host 'some console message'
invoke-some-utility arg1 arg2 | write-host
functionCall arg1 arg2 | out-null # suppress any output
return theDesiredValue
PowerShell does pipes awkwardkly. To work around the return value problem and save the pipeline for real data, pass the function a value by reference, that is, an array. The array can be loaded with the value you wish to return. Here's a simple example:
# tp.ps1
# test passed parameters
# show that arrays are passed by reference, and can be used to provide
# return values outside the pipeline
function doprint
{
process { write-output "value now: $psitem" }
}
function recurse($thing, $rtx)
{
$thing++
if($thing -lt 20) {
if($thing -eq 15) { $rtx[0] = $thing }
write-output $thing | doprint
recurse $thing $rtx
}
}
j=0
$rtv=#(4)
recurse $j $rtv
write-output $rtv[0]
I was surprised how powershell return works. Because I never had to write a function which returns a value. It returns not the variable itself, but all output stream of the function. To not pass output, you would have to use Out-Null. So your example would be:
function Calculate
{
. {
echo "Calculate"
$result = 11
return
} | Out-Null
return $result
}
$result = Calculate
And result is:
11
Credits to https://riptutorial.com/powershell/example/27037/how-to-work-with-functions-returns

In powershell is there a way to check if a user's input has a decimal in it?

I am writing a code and a part of it asks the user inputs a number and I have to check if the number is a decimal. If the number is a decimal than output has to say "this is a decimal number" and if the number is not a decimal than the output says "This is not a decimal number". Is there a way to do this?
Since the cat is out of the bag now you could just attempt to cast the value to a [decimal]. If successful return $true
function IsDecimal($value){
Try{
[decimal]$value | Out-Null
$true
} Catch {
$false
}
}
IsDecimal "10"
IsDecimal "1.2"
IsDecimal "bagels"
output
True
True
False
So you could use this in other parts of your code if need be
If(IsDecimal $string){Write-Host "Stuff"}
You can read more about casting in from this question. Lots of good answers here that don't need to be repeated.
In that regard
If($value -as [decimal]){Write-Host "Valid decimal"}else{Write-Host "Not valid decimal"}
I believe if you are trying to pass number from command line and checking then it would look something like below.
PS C:\> .\Untitled1.ps1 123
Does not contains '.'
PS C:\> .\Untitled1.ps1 123.24
Contains '.'
Untitled1.ps1
============
if ($args[0].GetType() -eq [System.Double])
{
Write-Host "Contains '.'";
}
else
{
Write-Host "Does not contains '.'";
}
Your question can work two ways. You are looking to determine is a number is a decimal. Eiter you mean the type or you mean if the number has decimals.
Lets work with variable $Sum and $Sum holds the value 2.5
In case you want to know the type you could just enter:
$sum.GetType().Name
In case you want a true or false you could enter:
if (($sum - ($sum -as [int])) -ne 0) {"$false"}Else {$True}
You see, by casting $Sum as an [int] it would automatically be rounded (in this case to 2). So by extracting ($Sum -as [int]) from $Sum you would end up with an outcome of 2.5-2 = 0.5 which means that sum must be anything else but a rounded number.