A Quick Lesson in Boolean

I often see scripts that are checking for True/False values using an If/Else statement like the one below.

if($value){
    'True'
} else { 
    'False'
}

While this technically will work for Boolean values, there are situations where this will not work. For example, if you pass the string value of “False” to the statement above it will evaluate as True. This is because the if condition in that code is not checking for True, it is checking for if exists. In that statement anything that is not Boolean False, Null, an empty string, or 0 will return True. However, if you specify a value must be true or false, PowerShell will auto-convert things like the string equal to ‘False’ as actually being false.

This is why I always recommend using the full condition of if($value -eq $true). Not only will this give you the ability to account for variables you may not be able to control, it also gives you a way to handle non-Boolean values. You can test it out for yourself using the snippet below, and see how the different ways of writing this If/Else statement work out.

Function Test-Boolean($value, $test){
    if($value -eq $true){
        $Result='True'
    } elseif ($value -eq $false) { 
        $Result='False'
    } else {
         $Result='Else'
    }

    if($value){
        $IfElse = 'True'
    } else { 
        $IfElse = 'False'
    }

    [pscustomobject]@{TrueFalse=$Result;IfElse=$IfElse;Value=$value;Test=$test}
}


Test-Boolean $true 'True boolean'
Test-Boolean 'true' 'True string'
Test-Boolean 1 '1 integer'
Test-Boolean $false 'False boolean'
Test-Boolean 'false' 'False string'
Test-Boolean 0 '0 integer'
Test-Boolean 'random' 'Any other string'
Test-Boolean 2 'Any other integer'
Test-Boolean ([string]::Empty) 'Empty string'
Test-Boolean ' ' 'Blank string'
Test-Boolean $null 'Null value'

After you run the snippet you should see results like the one below showing you how the two different If/Else statements evaluated the different values.

  |  |  
Fast Deploy Microsoft Teams for Education

Recently one of my colleagues came to me with an interesting situation. His wife runs a homeschool co-op. Basically, a group of parents who homeschool their children, with different parents covering different subjects. Due to COVID-19, he needed a quick way to get them setup in Office 365 and Teams. So, I put together a quick upload script to import via a CVS. After talking with others in a similar situation and find out about the COVID-19 hackathon last weekend, we decided to make a user friendly interface and fully packaged upload script. If you know someone else in a similar situation, please feel free to point them to this – https://devpost.com/software/fast-deploy-microsoft-teams-for-education

Properly Capitalize a Title Using PowerShell

Being married to someone who majored in English has made me extra conscious of my spelling and capitalization. So, for my blog posts, I’ve written a script to help me ensure my titles are properly capitalized. It is not perfect (i.e. it doesn’t do a dictionary lookup), but it follows the basic APA guidelines. I thought I would share it, in case others would find it useful.

# Split the string into individual words
$words = $string.Split()
[System.Collections.Generic.List[PSObject]]$stringArray = @()
For($i = 0; $i -lt $words.Count; $i++){
    # Capitalize words of four or more letters, the first word, and the last word
    if($words[$i].Length -gt 3 -or $i -eq 0 -or $i -eq ($words.Count - 1)){
        # account for hyphen and capitalize words before and after
        $words[$i] = @($words[$i].Split('-') | ForEach-Object{
            $_.Substring(0,1).ToUpper() + $_.Substring(1,$_.Length-1)
        }) -join('-')
    } 
    # and the capitalized string to the array
    $stringArray.Add($words[$i])
}
# join the words back together to form your title
$stringArray -join(' ')


Example

PS C:\> $string = 'properly capitalize a title using PowerShell'
>> $words = $string.Split()
>> [System.Collections.Generic.List[PSObject]]$stringArray = @()
>> For($i = 0; $i -lt $words.Count; $i++){
>>     # Capitalize words of four or more letters, the first word, and the last word
>>     if($words[$i].Length -gt 3 -or $i -eq 0 -or $i -eq ($words.Count - 1)){
>>         # account for hyphen and capitalize words before and after
>>         $words[$i] = @($words[$i].Split('-') | ForEach-Object{
>>             $_.Substring(0,1).ToUpper() + $_.Substring(1,$_.Length-1)
>>         }) -join('-')
>>     }
>>     # and the capitalized string to the array
>>     $stringArray.Add($words[$i])
>> }
>> $stringArray -join(' ')

Properly Capitalize a Title Using PowerShell

  
Remove First Entry in Fixed Array

Here is a quick little trick for removing the first entry in a fixed size array. This can be used when you receive the error message: Exception calling “RemoveAt” with “1” argument(s): “Collection was of a fixed size.”

# Remove first entry
$array = $array[1..($array.Length-1)]


Example

PS C:\> $array = 1..5
>> Write-Host 'Initial Value'
>> $array
>> $array = $array[1..($array.Length-1)]
>> Write-Host 'After Value'
>> $array

Initial Value
1
2
3
4
5
After Value
2
3
4
5

  
Search Intune for Devices with Application Installed

This script uses the GraphAPI to check all devices in Intune to see if they have a particular application installed.

$Application = "*PuTTY*"
$Username = '[email protected]'

Function Get-AuthToken {
    <#
    .SYNOPSIS
    This function is used to authenticate with the Graph API REST interface
    .DESCRIPTION
    The function authenticate with the Graph API Interface with the tenant name
    .EXAMPLE
    Get-AuthToken
    Authenticates you with the Graph API interface
    .NOTES
    NAME: Get-AuthToken
    #>
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [string]$User
    )
    
    $userUpn = New-Object "System.Net.Mail.MailAddress" -ArgumentList $User
    $tenant = $userUpn.Host
    
    $AadModule = Get-Module -Name "AzureAD" -ListAvailable
    if(-not $AadModule) {
        $AadModule = Get-Module -Name "AzureADPreview" -ListAvailable
    }
    
    if (-not $AadModule) {
            write-host
        throw("AzureAD Powershell module not installed..`n" +
        "Install by running 'Install-Module AzureAD' or 'Install-Module AzureADPreview' from an elevated PowerShell prompt")
    }
    
    # Getting path to ActiveDirectory Assemblies
    # If the module count is greater than 1 find the latest version
    if($AadModule.count -gt 1){
        $Latest_Version = ($AadModule | select version | Sort-Object)[-1]
        $aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version }
        
        # Checking if there are multiple versions of the same module found
        if($AadModule.count -gt 1){
            $aadModule = $AadModule | select -Unique
        }
    
        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
    
    } else {
        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
    }
    
    [System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
    [System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null
    $clientId = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"
    $redirectUri = "urn:ietf:wg:oauth:2.0:oob"
    $resourceAppIdURI = "https://graph.microsoft.com"
    $authority = "https://login.microsoftonline.com/$Tenant"
    
    $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
    $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"
    $userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($User, "OptionalDisplayableId")
    $authResult = $authContext.AcquireTokenAsync($resourceAppIdURI,$clientId,$redirectUri,$platformParameters,$userId).Result
    
    # If the accesstoken is valid then create the authentication header
    if($authResult.AccessToken){
        # Creating header for Authorization token
        $authHeader = @{
            'Content-Type'='application/json'
            'Authorization'="Bearer " + $authResult.AccessToken
            'ExpiresOn'=$authResult.ExpiresOn
        }
        return $authHeader
    } else {
        throw "Authorization Access Token is null, please re-run authentication..."
    }
}

Function Get-MsGraphData($Path) {
    $FullUri = "https://graph.microsoft.com/beta/$Path"
    [System.Collections.Generic.List[PSObject]]$Collection = @()
    $NextLink = $FullUri

    do {
        $Result = Invoke-RestMethod -Method Get -Uri $NextLink -Headers $AuthHeader
        if($Result.'@odata.count'){
            $Result.value | ForEach-Object{$Collection.Add($_)}
        } else {
            $Collection.Add($Result)
        }
        $NextLink = $Result.'@odata.nextLink'
    } while ($NextLink)

    return $Collection
}

# Get authentication token
$AuthHeader = Get-AuthToken -User $Username

# Get all devices in Intune
$AllDevices = Get-MsGraphData "deviceManagement/managedDevices"

# Get detected app for each device and check for app name
[System.Collections.Generic.List[PSObject]]$FoundApp = @()
$wp = 1
foreach($Device in $AllDevices) {
    Write-Progress -Activity "Found $($FoundApp.count)" -Status "$wp of $($AllDevices.count)" -PercentComplete $(($wp/$($AllDevices.count))*100) -id 1
    $AppData = Get-MsGraphData "deviceManagement/managedDevices/$($Device.id)?`$expand=detectedApps"
    $DetectedApp = $AppData.detectedApps | ?{$_.displayname -like $Application}
    if($DetectedApp){
        $DetectedApp | Select-Object @{l='DeviceName';e={$Device.DeviceName}}, @{l='Application';e={$_.displayname}}, Version, SizeInByte,
            @{l='LastSyncDateTime';e={$Device.lastSyncDateTime}}, @{l='DeviceId';e={$Device.id}} | Foreach-Object { $FoundApp.Add($_) }
    }
    $wp++
}
Write-Progress -Activity "Done" -Id 1 -Completed

$FoundApp | Sort-Object deviceName | Format-Table
  |  
Run PSExec From PowerShell

PowerShell remoting help in a lot of areas, but there are times when you need to use PSExec. For those instances, I’ve created a function that you can use to run a command on a remote machine using PSExec.

Function ExecutePsExec($computer, $command){
    $ping = Test-Connection $computer -Count 1 -Quiet
    if($ping){
        $StdOutput = (Join-path $env:temp "$($computer).txt")
        Start-Process -FilePath $psexec -ArgumentList "-s \\$computer $command" -Wait -RedirectStandardOutput $StdOutput -WindowStyle Hidden
        $Results = Get-Content $StdOutput -raw
        Remove-Item $StdOutput -Force
    } else {
        $Results = "Not online"
    }
    [pscustomobject]@{
        Computer = $computer
        Results = $Results
    }
}
# path to PsExec on your local machine
$script:psexec = "C:\Tools\PsExec.exe"

# the command to run
$command = 'cmd /c "powershell.exe -ExecutionPolicy ByPass \\SHARE01\Scripts\Demo.ps1"'

# execute the command on the remote computer
ExecutePsExec -computer 'MYPC01' -command $command
  
Homework – Double Digit Addition without Carrying
My son decided to pretend to be sick from school the other day, so after we finished all the work the teach gave us, I decided to make him some extra work. This code will generate a page with 25 double digit addition problems. They haven’t learn to carry over digits yet, so all sums are less than 10. Maybe someone else out there could get some benefits from it.
[System.Collections.Generic.List[PSObject]] $page = @()
$j = 0
$blank52 = "<td height=""52""> </td>`n" * 9
$blank20 = "<td height=""20""><br></td>`n" * 9
while($j -lt 5){
    [System.Collections.Generic.List[PSObject]] $rowA = @()
    [System.Collections.Generic.List[PSObject]] $rowB = @()
    $i=0
    while($i -lt 5){
        $a = Get-Random -Minimum 1 -Maximum 10
        $b = Get-Random -Minimum 0 -Maximum (9-$a+1)

        $c = Get-Random -Minimum 1 -Maximum 10
        $d = Get-Random -Minimum 0 -Maximum (9-$c+1)

        if(($b + $d) -gt 0){
            if($b -eq 0){$b=' '}
            $rowA.Add("<td class=""xl66"" height=""52"" width=""115"">$a$c</td>`n")
            $rowB.Add("<td class=""xl65"" height=""52"" width=""115"">+ $b$d</td>`n")
            $i++
        }
    }

    $tableRows = New-Object System.Text.StringBuilder
    $tableRows.Append('<tr height="52">') | Out-Null
    $tableRows.Append($rowA -join('<td class="xl66" width="15"><br>')) | Out-Null
    $tableRows.Append('</tr><tr height="52">') | Out-Null
    $tableRows.Append($rowB -join('<td class="xl66" width="15"><br>')) | Out-Null
    $tableRows.Append("</tr><tr height=""52"">$blank52</tr>") | Out-Null
    $page.Add($tableRows.ToString())
    $j++
}


$bodyTop = @'
<html><head><title>math</title><style>.xl65{mso-style-parent:style0;font-size:36.0pt;mso-number-format:"\@";
text-align:right;border-top:none;border-right:none;border-bottom:1.5pt solid windowtext;border-left:none;}
.xl66{mso-style-parent:style0;font-size:36.0pt;mso-number-format:"\@";text-align:right;}</style></head><body>
<table style="text-align: left; width: 635px; height: 60px;" border="0" cellpadding="0" cellspacing="0"><tbody>
'@

$bodyBotton = @'
</tbody></table><br><br></body></html>
'@

$bodyTop + $page -join("<tr height=""20"">$blank20</tr>") + $bodyBotton | out-file '.\math.html'
  
Encrypt All Azure Automation Variables
$ResourceGroupName = ''
$AutomationAccountName = ''

# Get all variables
$variables = Get-AzureRMAutomationVariable -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName

# parse through each unencrypted variable
Foreach($var in $variables | Where-Object{$_.Encrypted -ne $True}){
    
    # remove the unencrypted variable
    Remove-AzureRMAutomationVariable -ResourceGroupName $var.ResourceGroupName -AutomationAccountName $var.AutomationAccountName -Name $var.Name
    
    # recreate the variable, with the same values and encrypt it
    New-AzureRMAutomationVariable -ResourceGroupName $var.ResourceGroupName -AutomationAccountName $var.AutomationAccountName -Name $var.Name -Encrypted $True -Value $var.Value -Description $var.Description
}

Details

It is best practices to encrypt all of your Azure Automation Account variables. However, it is not possible to convert an unencrypted variable to an encrypted one. So this snippet will return all variables and if any are unencrypted, it will remove them, the recreate it as an encrypted variable.