UGDSBPSU.psm1
#Region './Public/Convert-AzADObjIDtoSid.ps1' 0 <# .DESCRIPTION This cmdlet will convert a Azure AD Object ID TO Sid .PARAMETER ObjectId Azure AD Object ID .EXAMPLE Convert AzADObject to Sid Convert-AzADObjIDtoSid -objectId <ID> #> function Convert-AzADObjIDtoSid{ [CmdletBinding()] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$ObjectId ) $bytes = [Guid]::Parse($ObjectId).ToByteArray() $array = New-Object 'UInt32[]' 4 [Buffer]::BlockCopy($bytes, 0, $array, 0, 16) $sid = "S-1-12-1-$array".Replace(' ', '-') return $sid } #EndRegion './Public/Convert-AzADObjIDtoSid.ps1' 21 #Region './Public/Convert-SidtoAzADObjID.ps1' 0 <# .DESCRIPTION This cmdlet will convert a SID to an Azure AD Object ID .PARAMETER sid SID of object .EXAMPLE Convert Sid to Object ID Convert-SidtoAzADObjID -sid <SID> #> function Convert-SidtoAzADObjID{ [CmdletBinding()] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$sid ) $text = $sid.Replace('S-1-12-1-', '') $array = [UInt32[]]$text.Split('-') $bytes = New-Object 'Byte[]' 16 [Buffer]::BlockCopy($array, 0, $bytes, 0, 16) [Guid]$guid = $bytes return $guid } #EndRegion './Public/Convert-SidtoAzADObjID.ps1' 22 #Region './Public/Disable-Chromebook.ps1' 0 <# .DESCRIPTION This cmdlet will use GAM to disable a chromebook by SN .PARAMETER gamLocation The location that GAM files are stored .PARAMETER serialNumber array of serial numbers that you want to disable .EXAMPLE Disable a Single SN Disable-Chromebook -gamLocation <GAM> -serialNumber <SN> Disable multiple SN Disable-Chromebook -gamLocation <GAM> -serialNumber @('SN','SN') #> function Disable-Chromebook{ [CmdletBinding()] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$gamLocation, [Parameter(Mandatory = $true)][string[]]$serialNumber ) $results = @{} try{ foreach($sn in $serialNumber){ # Attempt to run GAM script to disable chromebook. ($data = Invoke-Expression -Command "$($gamLocation) update cros cros_sn $($sn) action disable 2>&1") | Out-Null if($data -match "^.*Got 0 CrOS.*$"){ $results.Add($sn,"Device Not Found") } else{ $results.Add($sn,"Disabled") } } } catch{ throw $Error[0] } return $results } #EndRegion './Public/Disable-Chromebook.ps1' 38 #Region './Public/Enable-Chromebook.ps1' 0 <# .DESCRIPTION This cmdlet will use GAM to disable a chromebook by SN .PARAMETER gamLocation The location that GAM files are stored .PARAMETER serialNumber array of serial numbers that you want to disable .EXAMPLE Disable a Single SN Disable-Chromebook -gamLocation <GAM> -serialNumber <SN> Disable multiple SN Disable-Chromebook -gamLocation <GAM> -serialNumber @('SN','SN') #> function Enable-Chromebook{ [CmdletBinding()] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$gamLocation, [Parameter(Mandatory = $true)][string[]]$serialNumber ) $results = @{} try{ foreach($sn in $serialNumber){ # Attempt to run GAM script to disable chromebook. ($data = Invoke-Expression -Command "$($gamLocation) update cros cros_sn $($sn) action reenable 2>&1") | Out-Null if($data -match "^.*Got 0 CrOS.*$"){ $results.Add($sn,"Device Not Found") } else{ $results.Add($sn,"Enabled") } } } catch{ throw $Error[0] } return $results } #EndRegion './Public/Enable-Chromebook.ps1' 38 #Region './Public/Get-Chromebook.ps1' 0 <# .DESCRIPTION This cmdlet will use GAM installed on your system to query Google for chromebooks based on your query and fields .PARAMETER gamLocation The location that GAM files are stored .PARAMETER query The query that you want to use for returning your devices .PARAMETER fields What fields you want it to return .EXAMPLE Retrieve a list of devices with a specific query Get-Chromebook -gamLocation <GAMPATH> -query $query #> function Get-Chromebook{ [CmdletBinding()] [OutputType([System.Collections.Generic.List[PSCustomObject]])] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$gamLocation, [Parameter(Mandatory = $false)]$query, [Parameter(Mandatory = $false)]$fields ) # Setup the regex that we are using to attempt to properly parse the gam output $RegexOptions = [System.Text.RegularExpressions.RegexOptions] $csvSplit = '(,)(?=(?:[^"]|"[^"]*")*$)' # Create a blank list to store the data $devices = [System.Collections.Generic.List[PSCustomObject]]@() # Query Gam with the query/fields passed ($chromebooks = Invoke-Expression -Command "$($gamLocation) print cros query $($query) nolists fields $($fields) 2>&1") | Out-Null # Loop through the lines of the return. Ignore the first line as that should be the headers $startHere = $false for($x = 1; $x -lt $chromebooks.length; $x++) { if($startHere -eq $false){ if($chromebooks[$x] -match "^deviceId,serialNumber.*$"){ $startHere = $true } continue } # Split the line $device = [regex]::Split($chromebooks[$x], $csvSplit, $RegexOptions::ExplicitCapture).replace('"','') # Set the wanIP to null, if we can set it then we do, otherwise it stays null $wanIP = $null if($null -ne $device[8]){ try{$wanIP = ($device[8] | ConvertFrom-JSON).wanIpAddress} catch{} } # Populate custom object for list $obj = [PSCustomObject]@{ "Name" = $device[1] "network" = $wanIP "mac-address" = $device[6] "lastSync" = $device[2] "chromebook-model" = $device[4] "chromebook-os" = $device[5] "location" = $device[7] } $devices.Add($obj) | Out-Null } # Return list return $devices } #EndRegion './Public/Get-Chromebook.ps1' 62 #Region './Public/Get-GoogleOU.ps1' 0 <# .DESCRIPTION This cmdlet will use GAM installed on your system to query Google for the list of your OUs. .PARAMETER gamLocation The location that GAM files are stored .EXAMPLE Retrieve a list of OUs Get-GoogleOU -gamLocation <PATH TO GAM> #> function Get-GoogleOU{ [CmdletBinding()] [OutputType([System.Collections.Generic.List[PSCustomObject]])] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$gamLocation ) # The command that we want to run with GAM $command = "print orgs" # Invoke GAM with the path and command ($ouRaw = Invoke-Expression -Command "$($gamLocation) $($command) 2>&1") | Out-Null # Create a list object to store the results $ouList = [System.Collections.Generic.List[PSCustomObject]]@() # Go through the results of the gam call $skipRows = $true for($i = 1; $i -lt $ouRaw.Count; $i++){ # Skip rows until we hit the header row. This will remove some of the externious GAM output if($skipRows -eq $true){ if($ouRaw[$i] -eq "orgUnitPath,orgUnitId,name,parentOrgUnitId"){ $skipRows = $false } continue } # Split results by comma $ou = $ouRaw[$i] -split "," # Create a object for each line to store in the list $obj = [PSCustomObject]@{ 'orgUnitPath' = $ou[0] 'orgUnitId' = $ou[1] 'name' = $ou[2] 'parentOrgUnitId' = $ou[3] } $ouList.add($obj) | Out-Null } # return list of OUs return $ouList } #EndRegion './Public/Get-GoogleOU.ps1' 46 #Region './Public/Get-GraphAccessToken.ps1' 0 <# .DESCRIPTION This cmdlet is designed to use MSAL.PS to get an access token through an app registration, and then store in a global access token variable .PARAMETER clientID The client ID for the app registration used .PARAMETER clientSecret The secret used for the app registration .PARAMETER tenantID The tenant id for the app registration #> function Get-GraphAccessToken{ [CmdletBinding()] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$clientID, [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][SecureString]$clientSecret, [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$tenantID ) if($(Test-GraphAcessToken $global:accessToken)){ return $global:accessToken } $accessTokenVars = @{ "clientID" = $clientID "clientSecret" = $clientSecret "tenantID" = $tenantID "ForceRefresh" = $true } $global:accessToken = Get-MsalToken @accessTokenVars } #EndRegion './Public/Get-GraphAccessToken.ps1' 29 #Region './Public/Get-GraphHeader.ps1' 0 <# .DESCRIPTION This cmdlet is designed to format the graph header for the REST api calls .PARAMETER ConsistencyLevel This field will add the ConsistencyLevel variable to eventual #> function Get-GraphHeader{ [CmdletBinding()] param( [Parameter()][switch]$ConsistencyLevel ) if(!$(Test-GraphAcessToken $global:accessToken)){ throw "Please Call Get-GraphAccessToken before calling this cmdlet" } $headerVars = @{ Authorization = "Bearer $($global:accessToken.AccessToken)" "Content-Type" = "application/json" } if($ConsistencyLevel){ $headerVars.Add("ConsistencyLevel","eventual") } return $headerVars } #EndRegion './Public/Get-GraphHeader.ps1' 24 #Region './Public/Get-GraphMail.ps1' 0 <# .DESCRIPTION This cmdlet is designed to read email from a specific mailbox .PARAMETER emailAddress The email address of the account that we are reading from .PARAMETER folder The ID of the folder that we want to read from, if it is not the whole mailbox .PARAMETER unread If we want to return only unread email #> function Get-GraphMail{ [CmdletBinding()] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$emailAddress, [Parameter()][ValidateNotNullOrEmpty()][string]$folder, [Parameter()][switch]$unread ) if(!$(Test-GraphAcessToken $global:accessToken)){ throw "Please Call Get-GraphAccessToken before calling this cmdlet" } $headers = Get-GraphHeader $uri = "https://graph.microsoft.com/beta/users/$($emailAddress)/messages" $FilterList = [System.Collections.Generic.List[PSCustomObject]]@() if ($folder) { $FilterList.Add("parentFolderId eq '$($folderInID)'") | Out-Null } if ($unread) { $FilterList.Add("isRead eq false") | Out-Null } if($FilterList -ne "") { $FilterExpression = [scriptblock]::Create(($FilterList -join " and ")) $uri = "$($uri)?`$filter=$($FilterExpression)" } try{ $results = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -StatusCodeVariable statusCode return $results.value } catch{ throw "Unable to get emails from mailbox." } } #EndRegion './Public/Get-GraphMail.ps1' 43 #Region './Public/Get-KeyVaultSecret.ps1' 0 <# .DESCRIPTION This cmdlet is used to connect to a keyvault as the machine identity of the Azure Machine it is running under .PARAMETER vaultName The name of the vault that we want to check .PARAMETER secretName The name of the secret we want to recover .EXAMPLE Check a secret out of a specific vault Get-KeyVaultSecret -vaultName <VAULT> -secretName <SECRET> #> function Get-KeyVaultSecret{ [CmdletBinding()] [OutputType([System.String])] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$vaultName, [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$secretName ) # The URI for the vault that we want to access $keyVaultURI = "https://$($vaultName).vault.azure.net/secrets/$($secretName)?api-version=2016-10-01" # Using the identity of the virtual machine account running the script $response = Invoke-RestMethod -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fvault.azure.net' -Method GET -Headers @{Metadata="true"} # What the vault token is $keyVaultToken = $response.access_token try{ # Get the relevant secret and return it $secret = Invoke-RestMethod -Uri $keyVaultURI -Method GET -Headers @{Authorization="Bearer $KeyVaultToken"} return $secret.Value | ConvertFrom-Json } # Error handling possible expected errors catch{ if(($Error[0] -match "The remote name could not be resolved")){ $message = "Error: Attempting to connect to Azure Key vault URI $($keyVaultURI)`n$($_)" } elseif(($Error[0] -match "Unauthorized")){ $message = "Error: No authorization to Azure Key Vault URI $($keyVaultURI)`n$($_)" } elseif(($Error[0] -match "SecretNotFound")){ $message = "Error: The secret $($secretName) is not found in Azure Key Vault URI $($keyVaultURI)`n$($_)" } else{ $message = "Error: Unknown error connection to Azure Key vault URI $($keyVaultURI)`n$($_)" } Write-EventLog -LogName "Application" -Source "PowerShell Universal Scripts" -EntryType "Warning" -EventId 1001 -Message $message return $message } } #EndRegion './Public/Get-KeyVaultSecret.ps1' 48 #Region './Public/Get-Values.ps1' 0 <# .DESCRIPTION This cmdlet is designed to help PSU scripts for display of values .PARAMETER fields What fields to display #> function Get-Values(){ param( [Parameter(Mandatory)][string[]]$fields, [Parameter(Mandatory)][PSCustomObject]$item ) $value = "" # Loop through the array to take not of what values were selected, and then return that value. foreach($i in $fields){ if($item.$i){ if($i.Contains('-1')){$value += $i.Substring(0,$i.Length-2) + ","} else{$value += $i + ","} } } return $value.Replace("-"," ") } #EndRegion './Public/Get-Values.ps1' 22 #Region './Public/Get-YesNo.ps1' 0 <# .DESCRIPTION This cmdlet is designed to help PSU scripts for display of yes/no .PARAMETER fields What fields to display #> function Get-YesNo(){ [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory = $true)][string[]]$fields, [Parameter(Mandatory)][PSCustomObject]$item ) # Loop through values that are passed, and if exist, return yes, otherwise return No foreach($i in $fields){ if($item.$i){return "Yes"} } return "No" } #EndRegion './Public/Get-YesNo.ps1' 20 #Region './Public/Move-GraphMail.ps1' 0 <# .DESCRIPTION This cmdlet is designed to move emails between folders in a mailbox .PARAMETER id The id of the mail message we are acting on .PARAMETER emailAddress The email address of the account that we are reading from .PARAMETER folder The id of the folder that we are moving the message to #> function Move-GraphMail{ [CmdletBinding()] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$id, [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$emailAddress, [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$folder ) if(!$(Test-GraphAcessToken $global:accessToken)){ throw "Please Call Get-GraphAccessToken before calling this cmdlet" } $headers = Get-GraphHeader # Body Content $body = @{ "destinationId" = $folder } | ConvertTo-Json $uri = "https://graph.microsoft.com/beta/users/$($emailAddress)/messages/$($id)/move" $results = Invoke-RestMethod -Method Post -Uri $uri -Headers $headers -Body $body -StatusCodeVariable statusCode # Return Results if($statusCode -in (200,201)){ return $results } else{ throw "Unable to move email." } } #EndRegion './Public/Move-GraphMail.ps1' 36 #Region './Public/New-RandomString.ps1' 0 <# .DESCRIPTION This cmdet will generate a random character string based on inputs passed to it. .PARAMETER length The number of characters you want the random string to contain. .PARAMETER characters The list of characters that you want it to use to generate the random string .EXAMPLE New-RandomString -length 10 -characters 'abcdefghiklmnoprstuvwxyzABCDEFGHKLMNOPRSTUVWXYZ1234567890!@#$%^&*' Will Generate a random string of 10 characters in length with the characters in abcdefghiklmnoprstuvwxyzABCDEFGHKLMNOPRSTUVWXYZ1234567890!@#$%^&* #> function New-RandomString{ [CmdletBinding()] [OutputType([System.String])] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][int]$length, [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$characters ) # Generate a random string based on the length and characters passed $randomString = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length} $private:ofs = "" return [string]$characters[$randomString] } #EndRegion './Public/New-RandomString.ps1' 24 #Region './Public/Set-GraphMailRead.ps1' 0 <# .DESCRIPTION This cmdlet is designed to mark a specific email as read .PARAMETER id The id of the mail message we are acting on .PARAMETER emailAddress The email address of the account that we are reading from #> function Set-GraphMailRead{ [CmdletBinding()] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$id, [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$emailAddress ) if(!$(Test-GraphAcessToken $global:accessToken)){ throw "Please Call Get-GraphAccessToken before calling this cmdlet" } $headers = Get-GraphHeader # Body Content $body = @{ "isRead" = $true } | ConvertTo-Json # Execute Graph Call $uri = "https://graph.microsoft.com/beta/users/$($emailAddress)/messages/$($id)" $results = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $body -StatusCodeVariable statusCode # Return Results if($statusCode -in (200,201)){ return $results } else{ throw "Unable to mark email as read." } } #EndRegion './Public/Set-GraphMailRead.ps1' 34 #Region './Public/Test-AllowedGroupMember.ps1' 0 <# .DESCRIPTION This cmdlet is used to check to see if a specific user belongs to a group that is passed .PARAMETER groupList Array of the groups to check .PARAMETER domain If active directory, what domain to check. If you use this, it ignores any of the Az parameters .PARAMETER AzAppRegistration The client id of the azure app registration working under .PARAMETER AzTenant The directory id for the Azure AD tenant .PARAMETER AzSecret The client secret used to connect to MS Graph .EXAMPLE Check for a specific user in active directory Test-AllowedGroupMember -userPrincipalName <UPN> -groupList @("GROUPNAME") -domain <DOMAIN> Check for a specific user in Azure AD group Test-AllowedGroupMember -userPrincipalName <UPN> -groupList @("GROUPNAME") -AzTenant $AzTenant -AzAppRegistration $AzAppRegistration -AzSecret $Secret #> function Test-AllowedGroupMember{ [CmdletBinding()] [OutputType([System.Boolean])] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$userPrincipalName, [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][Object[]]$groupList, [Parameter()][string]$domain, [Parameter()][string]$AzAppRegistration, [Parameter()][string]$AzTenant, [Parameter()][pscredential]$AzSecret ) # Nested function to be able to recurse through groups in Azure AD since Get-MGGroupMembers does not have this function currently function getNestedMembers{ param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$groupId, [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$userPrincipalName ) # Set found to false $results = $false # Get memberes of the group that was passed $members = Get-MgGroupMember -All -GroupId $groupId # If the username is found return true if($userPrincipalName -in $members.AdditionalProperties.userPrincipalName){ return $true } # If not found, check the list for other nested groups else{ $groups = $members | where-object {$_.AdditionalProperties.'@odata.type' -eq "#microsoft.graph.group"} # Loop through those groups those nested function foreach($group in $groups){ $results = getNestedMembers -groupId $groupId -userPrincipalName $userPrincipalName if($results -eq $true){ # if the results returned are true, return true. return $true } } } } # If set to query Azure AD Groups connect to MS Graph if($AzAppRegistration){ # Connect to MS Graph $msalToken = Get-MsalToken -clientID $AzAppRegistration -clientSecret $AzSecret.Password -tenantID $AzTenant Connect-MgGraph -AccessToken $msalToken.AccessToken | Out-Null } foreach($group in $groupList){ try{ if($domain){ # Get all the members and nested members of the group in Active Directory $members = Get-ADGroupMember -Recursive -Server $domain -Identity $group -ErrorAction SilentlyContinue | where-object {$_.objectClass -eq 'User'} | Get-ADUser | select-object UserPrincipalName # Check to see if the list contains the expected UPN and if return true if($members.UserPrincipalName -contains $userPrincipalName){ return $true } } else{ # Get the group from Azure AD $groups = Get-MGgroup -Filter "DisplayName eq '$($group)'" # Loop through if there are multiple groups with the same name foreach($group in $groups){ # Get the results of the function to recurse through the groups $results = getNestedMembers -groupId $group.id -userPrincipalName $userPrincipalName # Return true if correct if($results -eq $true){ return $true } } } } catch{ throw "An error occured while processing group $($group) : $($Error[0])" } } return $false } #EndRegion './Public/Test-AllowedGroupMember.ps1' 95 #Region './Public/Test-GraphAcessToken.ps1' 0 <# .DESCRIPTION This cmdlet is tests to see if the passed variable is not null, and expires in less than 10 minutes .PARAMETER token The current access tokenb variable #> function Test-GraphAcessToken{ [CmdletBinding()] param( [Parameter()][System.Object]$token ) if(!$token){ return $false } $expiryTime = $token.ExpiresOn - (Get-Date) if($expiryTime.Minutes -lt 10){ return $false } else{ return $true } } #EndRegion './Public/Test-GraphAcessToken.ps1' 23 #Region './Public/Test-SamAccountName.ps1' 0 <# .DESCRIPTION This cmdlet is designed to check Active Directory for a valid samAccountName when creating/changing user. .PARAMETER samAccountName The account name we want to test for .PARAMETER server The server that we want to test against .EXAMPLE Check for a specific samAccountName Test-SamAccountName -samAccountName <NAME> -server <SERVER> #> function Test-SamAccountName{ [CmdletBinding()] [OutputType([System.String],[System.Boolean])] param( [Parameter(Mandatory = $true)]$samAccountName, [Parameter(Mandatory = $true)]$server ) # Default Addition at the end of the name if it exists. $postFix = 2 # Loop through to try to find a valid samAccountName or fail if loops too many times do{ try{ # Check to see if the user already exists. Get-ADUser -Identity $samAccountName -Server $server | Out-Null # If it does exist, then add the postfix if($postFix -eq 2){ $samAccountName = "$($samAccountName)$($postFix)" } # If postfix is greater than default, then remove it (as we max at 9) to add the new postfix else{ $samAccountName = "$($samAccountName.substring(0,$samAccountName.length -1))$($postFix)" } } # If the account doesn't exist, return the samAccountName as good catch [Microsoft.ActiveDirectory.Management.ADIdentityResolutionException] { return $samAccountName } catch { throw $Error[0] } $postFix++ }while($postFix -lt 10) # Return false if we couldn't find a valid samAccountName we could use return $false } #EndRegion './Public/Test-SamAccountName.ps1' 47 |