powershell function that prints letter grade with a range [closed] - powershell

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 4 years ago.
Improve this question
so far i got this need help writing this function:
if (grades$Final[i] >=90) {
"A"
}
else if (grades$Final[i] >=80){
"B"
}
else if(grades$Final[i] >=70){
"C"
}
else if(grades$Final[i] >=60){
"D"
}
else {"F"}
}
}

Hello in this case i would suggest using a switch instead of a bunch of If statements
In powershell we use -lt (less than) -gt (Greater than) -eq (Equal) -ge (Greater or equal) -le (Less than or equal)
I noticed you are using [i] to call the index in an array i assume.
In powershell you can pipe |. So You can take an array and pipe it to a function. You can create an array using #().
#("Hello","World") | Foreach-object{
"$_ TEST"
}
Output
Hello TEST
World TEST
So i bet you are wondering what $_ is. Well When you pipe its the object that was piped so as in the example above since the array had 2 entries the first loop it equaled Hello and the second time it equaled World
So here is a function to get Int to Grade Letters. As you can see at bottom piping Ints into the function
function Get-LetterGrade(){
param(
[Parameter(Position=1, ValueFromPipeline=$true)]
[int]$Grade
)
process{
switch($Grade){
{$_ -ge 90} { "A" }
{$_ -ge 80 -and $_ -lt 90} { "B" }
{$_ -ge 70 -and $_ -lt 80} { "C" }
{$_ -ge 60 -and $_ -lt 70} { "D" }
{$_ -lt 60} { "F" }
}
}
}
90,80,70,60,50 | Get-LetterGrade
Output would be
A
B
C
D
F

In Powershell numeric comparison operators are a little different...
is -gt
< is -lt
= is -ge
There is a ton (...ton) of documentation on comparison operators in Powershell, which you can get via Google.
"else if" would be "elseif". Your variable name is a little weird. In Powershell, we start variable names with a dollar sign, "$". It sort of looks like you have an array named "grades$Final". It's confusing. Stick to conventions on variable names; descriptive and adhering to naming rules.
If I had to re-write your logic, I'd use something like...
$grades = #(12, 78, 83, 92)
foreach ($grade in $grades) {
if ($grade -ge 90) {
write-output 'A'
} elseif ($grade -lt 90 -and $grade -ge 80) {
write-output 'B'
} elseif ($grade -lt 80 -and $grade -ge 70) {
write-output 'C'
} elseif ($grade -lt 70 -and $grade -ge 60) {
write-output 'D'
} elseif ($grade -lt 60) {
write-output 'F'
}
}

Related

PowerShell While Loop Multiple Conditions not working

Want to check multiple conditions in a while loop, but they don't work.
#Debug
if ($DatumArray -notcontains $DatumAktuellerFeiertag) {echo "true"} else {echo "false"}
if ($TagAktuellerFeiertag -ne "Samstag") {echo "true"} else {echo "false"}
if ($TagAktuellerFeiertag -ne "Sonntag") {echo "true"} else {echo "false"}
The above code gives the following result:
true
false
true
Notice, one of the results is "false".
while (($DatumArray -notcontains $DatumAktuellerFeiertag) -and ($TagAktuellerFeiertag -ne "Samstag") -and ($TagAktuellerFeiertag -ne "Sonntag")) {
# some code...
}
The loop is not performed, even though one of the results is "false".
What is the possible way to archive my goal? Why is this while loop not working?
Edit:
This working not as expected, because you not know my condition I think. So I will try to explain:
Got an array with public holidays $DatumArray (01.01.2019, 19.04.2019, 21.04.2019 like this...).
$DatumAktuellerFeiertag is the actual public holiday date.
$TagAktuellerFeiertag is the actual public holiday weekday.
Now I'm trying to figure out the next working day (but if the next working day a public holiday too, it has to consider that).
So my condition will be like: while there is a public holiday OR a Saturday OR a Sunday, increment $DatumAktuellerFeiertag by 1.
while (($DatumArray -notcontains $DatumAktuellerFeiertag) -and (($TagAktuellerFeiertag -ne "Samstag") -or ($TagAktuellerFeiertag -ne "Sonntag"))) {
$DatumAktuellerFeiertag = (Get-Date $DatumAktuellerFeiertag).AddDays(1).ToString("dd/MM/yyy")
$TagAktuellerFeiertag = (Get-Date $DatumAktuellerFeiertag -Format "dddd")
echo $DatumAktuellerFeiertag
}
Edit:
Tried your version, works on "normal" days flawless, but gives me a endless loop at a public holiday.
$ListPublicHoliday = Import-Csv 'datum.csv'
$DateArray = $ListPublicHoliday.Datum
$DateArray = $DateArray | ForEach-Object { (Get-Date $_).Date }
$ActuallyDay = Get-Date 19.04.2019
while (($DateArray -contains $ActuallyDay.Date) -or ('Samstag', 'Sonntag' -contains $ActuallyDay.DayOfWeek)) {
$ActuallyDay.AddDays(1)
}
My CSV:
#TYPE Selected.System.String
"Datum","Feiertag","Wochentag","Value"
"01.01.2019","Neujahrstag","Dienstag","01.01.2019 18:33:01"
"19.04.2019","Karfreitag","Freitag","19.04.2019 18:33:01"
"21.04.2019","Ostersonntag","Sonntag","21.04.2019 18:33:01"
PS: can you explain me this? (Get-Date $_).Date? I didn't find this on Microsoft docs.
The loop is not performed, even though one of the results is "false". [...] Why is this while loop not working?
The loop doesn't work because one of the results is $false. Your condition consists of 3 clauses connected with -and operators, meaning that all of the clauses must evaluate to $true for the loop to operate. However, since your 2nd and 3rd clause are mutually exclusive, that will never happen.
I'm not quite sure what your condition is supposed to look like, but at the very least you need the condition to be A && (B || C) rather than A && B && C.
Change this:
while (($DatumArray -notcontains $DatumAktuellerFeiertag) -and ($TagAktuellerFeiertag -ne "Samstag") -and ($TagAktuellerFeiertag -ne "Sonntag")) {
# some code...
}
into this:
while (($DatumArray -notcontains $DatumAktuellerFeiertag) -and (($TagAktuellerFeiertag -ne "Samstag") -or ($TagAktuellerFeiertag -ne "Sonntag"))) {
# some code...
}
Edit:
After you updated your question, clarifying what you're actually trying to accomplish, your clauses should indeed be connected with -or operators, as Mathias suspected in the comments. However, the operators inside the clauses must not be negated on top of that (you need -contains and -eq instead of -notcontains and -ne). Also, your code would become a lot simpler if $DatumArray contained DateTime objects instead of strings. The two weekday comparisons could be combined in one too.
Something like this should work:
$DatumArray = $DatumArray | ForEach-Object { (Get-Date $_).Date }
$startDate = ...
$cur = Get-Date $startDate
while (($DatumArray -contains $cur.Date) -or ('Samstag', 'Sonntag' -contains $cur.DayOfWeek)) {
$cur = $cur.AddDays(1)
}

How do I preserve Enums as 'Strings' in ConvertTo-Json cmdlet? [duplicate]

For "Get-Msoldomain" powershell command-let I get the below output (lets call it Output#1) where Name, Status and Authentication are the property names and below are their respective values.
Name Status Authentication
myemail.onmicrosoft.com Verified Managed
When I use the command with "ConvertTo-Json" like below
GetMsolDomain |ConvertTo-Json
I get the below output (lets call it Output#2) in Json Format.
{
"ExtensionData": {
},
"Authentication": 0,
"Capabilities": 5,
"IsDefault": true,
"IsInitial": true,
"Name": "myemail.onmicrosoft.com",
"RootDomain": null,
"Status": 1,
"VerificationMethod": 1
}
However, the problem is, that if you notice the Status property in both the outputs, it's different. Same happens for VerificationMethod property. Without using the ConvertTo-JSon Powershell gives the Text, and with using ConvertTo-Json it gives the integer.
When I give the below command
get-msoldomain |Select-object #{Name='Status';Expression={"$($_.Status)"}}|ConvertTo-json
I get the output as
{
"Status": "Verified"
}
However, I want something so that I don't have to specify any specific property name for it to be converted , the way I am specifying above as
Select-object #{Name='Status';Expression={"$($_.Status)"}}
This line is transforming only the Status Property and not the VerificationMethod property because that is what I am providing as input .
Question: Is there something generic that I can give to the "ConvertTo-Json" commandlet, so that It returns ALL the Enum properties as Texts and not Integers, without explicitly naming them, so that I get something like below as the output:
{
"ExtensionData": {
},
"Authentication": 0,
"Capabilities": 5,
"IsDefault": true,
"IsInitial": true,
"Name": "myemail.onmicrosoft.com",
"RootDomain": null,
"Status": "Verified",
"VerificationMethod": "DnsRecord"
}
Well, if you don't mind to take a little trip :) you can convert it to CSV which will force the string output, then re-convert it back from CSV to PS Object, then finally back to Json.
Like this:
Get-MsolDomain | ConvertTo-Csv | ConvertFrom-Csv | ConvertTo-Json
If you need to keep the original Types instead of converting it all to string see mklement0 helpful answer...
PowerShell Core (PowerShell versions 6 and above) offers a simple solution via ConvertTo-Json's -EnumsAsStrings switch.
GetMsolDomain | ConvertTo-Json -EnumsAsStrings # PS *Core* (v6+) only
Unfortunately, this switch isn't supported in Windows PowerShell.
Avshalom's answer provides a quick workaround that comes with a big caveat, however: All property values are invariably converted to strings in the process, which is generally undesirable (e.g., the Authentication property's numeric value of 0 would turn into string '0').
Here's a more generic workaround based on a filter function that recursively introspects the input objects and outputs ordered hashtables that reflect the input properties with enumeration values converted to strings and all other values passed through, which you can then pass to ConvertTo-Json:
Filter ConvertTo-EnumsAsStrings ([int] $Depth = 2, [int] $CurrDepth = 0) {
if ($_ -is [enum]) { # enum value -> convert to symbolic name as string
$_.ToString()
} elseif ($null -eq $_ -or $_.GetType().IsPrimitive -or $_ -is [string] -or $_ -is [decimal] -or $_ -is [datetime] -or $_ -is [datetimeoffset]) {
$_
} elseif ($_ -is [Collections.IEnumerable] -and $_ -isnot [Collections.IDictionary]) { # enumerable (other than a dictionary)
, ($_ | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth+1))
} else { # non-primitive type or dictionary (hashtable) -> recurse on properties / entries
if ($CurrDepth -gt $Depth) { # depth exceeded -> return .ToString() representation
Write-Warning "Recursion depth $Depth exceeded - reverting to .ToString() representations."
"$_"
} else {
$oht = [ordered] #{}
foreach ($prop in $(if ($_ -is [Collections.IDictionary]) { $_.GetEnumerator() } else { $_.psobject.properties })) {
if ($prop.Value -is [Collections.IEnumerable] -and $prop.Value -isnot [Collections.IDictionary] -and $prop.Value -isnot [string]) {
$oht[$prop.Name] = #($prop.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth+1))
} else {
$oht[$prop.Name] = $prop.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth+1)
}
}
$oht
}
}
}
Caveat: As with ConvertTo-Json, the recursion depth (-Depth) is limited to 2 by default, to prevent infinite recursion / excessively large output (as you would get with types such as [System.IO.FileInfo] via Get-ChildItem, for instance). Similarly, values that exceed the implied or specified depth are represented by their .ToString() value. Use -Depth explicitly to control the recursion depth.
Example call:
PS> [pscustomobject] #{ p1 = [platformId]::Unix; p2 = 'hi'; p3 = 1; p4 = $true } |
ConvertTo-EnumsAsStrings -Depth 2 |
ConvertTo-Json
{
"p1": "Unix", # Enum value [platformId]::Unix represented as string.
"p2": "hi", # Other types of values were left as-is.
"p3": 1,
"p4": true
}
Note: -Depth 2 isn't necessary here, given that 2 is the default value (and given that the input has depth 0), but it is shown here as a reminder that you may want to control it explicitly.
If you want to implement custom representations for additional types, such as [datetime], [datetimoffset] (using the ISO 8601-compatible .NET round-trip date-time string format, o, as PowerShell (Core) v6+ automatically does), as well as [timespan], [version], [guid] and [ipaddress], see Brett's helpful variation of this answer.
I needed to serialize pwsh objects to JSON, and was not able to use the -EnumsAsStrings parameter of ConvertTo-Json, as my code is running on psv5. As I encountered infinite loops while using #mklement0's code Editor's note: since fixed., I rewrote it. My revised code also deals with the serialization of some other types such as dates, serializing them into the ISO 8601 format, which is generally the accepted way to represent dates in JSON. Feel free to use this, and let me know if you encounter any issues.
Filter ConvertTo-EnumsAsStrings ([int] $Depth = 10, [int] $CurrDepth = 0) {
if ($CurrDepth -gt $Depth) {
Write-Error "Recursion exceeded depth limit of $Depth"
return $null
}
Switch ($_) {
{ $_ -is [enum] -or $_ -is [version] -or $_ -is [IPAddress] -or $_ -is [Guid] } {
$_.ToString()
}
{ $_ -is [datetimeoffset] } {
$_.UtcDateTime.ToString('o')
}
{ $_ -is [datetime] } {
$_.ToUniversalTime().ToString('o')
}
{ $_ -is [timespan] } {
$_.TotalSeconds
}
{ $null -eq $_ -or $_.GetType().IsPrimitive -or $_ -is [string] -or $_ -is [decimal] } {
$_
}
{ $_ -is [hashtable] } {
$ht = [ordered]#{}
$_.GetEnumerator() | ForEach-Object {
$ht[$_.Key] = ($_.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth + 1))
}
if ($ht.Keys.Count) {
$ht
}
}
{ $_ -is [pscustomobject] } {
$ht = [ordered]#{}
$_.PSObject.Properties | ForEach-Object {
if ($_.MemberType -eq 'NoteProperty') {
Switch ($_) {
{ $_.Value -is [array] -and $_.Value.Count -eq 0 } {
$ht[$_.Name] = #()
}
{ $_.Value -is [hashtable] -and $_.Value.Keys.Count -eq 0 } {
$ht[$_.Name] = #{}
}
Default {
$ht[$_.Name] = ($_.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth + 1))
}
}
}
}
if ($ht.Keys.Count) {
$ht
}
}
Default {
Write-Error "Type not supported: $($_.GetType().ToString())"
}
}
}

Finding Middle Number in PowerShell

I am practicing in PowerShell and am making a user response input where one of the options is to input 3 numbers, and the program will return the middle number. I have done this a million times and it seems I cannot get it to return the middle number consistently.
For example when my numbers are 1, 23452342 and 3, it says that 3 is the middle number.
Here is my code:
if ($response -eq 1) {
$a = Read-Host "Enter a number "
$b = Read-Host "Enter a second number "
$c = Read-Host "Enter a third number "
if (($a -gt $b -and $a -lt $c) -or ($a -lt $b -and $a -gt $c)) {
Write-Host "$a is the middle number"
}
if (($b -gt $a -and $b -lt $c) -or ($b -gt $c -and $b -lt $a)) {
Write-Host "$b is the middle number"
}
if (($c -gt $a -and $c -lt $b) -or ($c -gt $b -and $c -lt $a)) {
Write-Host "$c is the middle number"
}
}
Instead of doing a number of individual comparisons simply sorting the three values and picking the second element will give you the median right away. But I suspect what's actually messing up the results for you is that Read-Host returns strings when you need them to be numeric values. Sort order of strings ("1" < "20" < "3") is different from numeric sort order (1 < 3 < 20), because characters at corresponding positions are compared rather than the whole number.
Casting the entered values to integers (or doubles if you expect floating point numbers) should resolve the issue:
if ($response -eq 1) {
[int]$a = Read-Host 'Enter a number'
[int]$b = Read-Host 'Enter a second number'
[int]$c = Read-Host 'Enter a third number'
$n = ($a, $b, $c | Sort-Object)[1]
Write-Host "$n is the median."
}
As an additional solution that would work on any array where u need the middle item you could just solve it like this:
$arr = 1..50
($arr | Sort-Object)[[int](($arr.count -1) /2)]
If your array comes in a format that does not need the sorting, just leave this part out.
edit: Obviously you would have to insert the data into an array on the first step.
Best regards

Switch handling with global arrays

Say I have 2 global arrays:
$Global:Values = #(1..100)
$Global:Test = #(75, 50, 25, 101)
Then I create a Switch by piping $Global:Test into a ForEach loop by using different conditions which I tried troubleshooting to get any kind of response:
$Global:Test | ForEach-Object ($_) {
$_
Switch ($_) {
($_ -contains $Global:Values) {Write-Host "Excellent"}
($_ -in $Global:Values) {Write-Host "Satisfactory"}
($_ -eq "25") {Write-Host "Unsatisfactory"}
Default {Write-Host "Invalid Grade"}
}
}
Output:
75
Invalid Grade
50
Invalid Grade
25
Invalid Grade
101
Invalid Grade
All of these switch statements do not work except the default. I don't know what I'm missing, but I'm sure it's a simple mistake I'm overlooking. Can anyone help me spot this mistake?
The foreach() statement is not the same as the ForEach-Object cmdlet, and you seem to be combining the syntax of both.
Additionally, you're using the switch statement incorrectly.
The part you're trying to match against needs to be a value:
switch ($something)
{
5 { "5 thing" }
(10 + 10) { "20 thing" }
}
or a scriptblock expression that returns a truthy or falsey value:
switch ($something)
{
{ $_ -gt 5 } { "do > 5 thing" }
{ Test-SomeCondition -Value $_ } { "do other thing" }
}
So you want the scriptblock form, but you're using parentheses.
So here's the ForEach-Object form:
$Global:Test | ForEach-Object {
Switch ($_) {
{$_ -contains $Global:Values} {Write-Host "Excellent"}
{$_ -in $Global:Values} {Write-Host "Satisfactory"}
"25" {Write-Host "Unsatisfactory"}
Default {Write-Host "Invalid Grade"}
}
}
or the foreach form:
foreach($value in $Global:Test) {
Switch ($value) {
{$_ -contains $Global:Values} {Write-Host "Excellent"}
{$_ -in $Global:Values} {Write-Host "Satisfactory"}
"25" {Write-Host "Unsatisfactory"}
Default {Write-Host "Invalid Grade"}
}
}
Also of note, the switch statement can actually work directly with arrays, so you don't have to iterate first. Basic example:
switch (1..10)
{
{ $_ -gt 5 } { "Second Half"}
default: { "First Half" }
}
So with yours, maybe:
Switch ($Global:Test) {
{$_ -contains $Global:Values} {Write-Host "Excellent"}
{$_ -in $Global:Values} {Write-Host "Satisfactory"}
"25" {Write-Host "Unsatisfactory"}
Default {Write-Host "Invalid Grade"}
}
Small addendum: stop using Global variables!

Powershell - Check number for several ranges

Hi I am new to Powershell and need some help:
My Script gets a number by userinput. I want to check this number if it´s between these ranges. So far so easy, but the input from 1-9 is with a leading zero.
With google, I got this working without the special case "leading zero".
do {
try {
$numOk = $true
$input = Read-host "Write a number between 1-12"
} # end try
catch {$numOK = $false }
} # end do
# check numbers
until (
($input -ge 1 -and $input -lt 13) -or
($input -ge 21 -and $input -lt 25) -or
($input -ge 41 -and $input -lt 49) -or
($input -ge 61 -and $input -lt 67) -and $numOK)
Write-Host $input
For example:
Input "5" Output "5"
Input "05" stucks in loop, should be "05"
AddOn: Is it possible to block inputs like 1-9 and just accept inputs like 01-09?
you can use the -f operator to format the string here is to get a 2 digits number
PS>"{0:00}" -f 5
05
some readings : http://ss64.com/ps/syntax-f-operator.html