UGDSBPSU.psm1

#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-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/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/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-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