<#
  .SYNOPSIS
  uberAgent script to collect information about the installed browser extensions for the current user.

  .DESCRIPTION
  Parses the browser profiles on disk to retrieve information about installed extensions.

  .Notes
  SOURCE:  https://github.com/vastlimits/uberAgent-Scripts/blob/main/Get-BrowserExtensionInfo/Get-BrowserExtensionInfo.ps1

  Modified by Sherry Kissinger
  Date:  2023-06-30
  Change Notes:  From the original script above, added:
   - Write to custom namespace and class (Namespace needs to have been PRE-CREATED, and PERMISSIONS opened via seperate run-as-system powershell)
   - Remove wmi entries for the current user before re-writing new entries; this is to retain OTHER entries for other users of the device.

  Script runs in USER context.
#>

#
# Global variables and types
#
enum Browser
{
   Chrome
   Edge
   Firefox
}

# WMI Variables
$WMINamespace = 'CustomCMClasses'
$WMIClass = 'Browser_Extensions'

# ScriptLastRan

$ScriptRanDate = Get-Date
$ScriptRan = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($ScriptRanDate)

##JustOneLogFile
[string]$LogFullPath = "$env:TEMP\$($WMIClass).log";

# Function to create the Browser_Extensions Class
Function CreateClass{
    $NewClass = New-Object System.Management.ManagementClass("root\$WMINamespace", [string]::Empty, $null)
    $NewClass.name = $WMIClass
    $NewClass.Qualifiers.Add("Static", $true)
    $NewClass.Properties.Add("OSUser", [System.Management.CimType]::string, $false)
    $NewClass.Qualifiers.Add("Description","Browser_Extensions stores information on extensions add in Edge, Firefox, or Chrome.")
    $NewClass.Properties.Add("Browser", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ProfileDir", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ProfileName", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ProfileGaiaName", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ProfileUserName", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ExtensionID", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ExtensionName", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ExtensionVersion", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ExtensionFromWebStore", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ExtensionState", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ExtensionInstallTime", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ExtensionInstalledByDefault", [System.Management.CimType]::String, $false)
    $NewClass.Properties.Add("ScriptLastRan",[System.Management.CimType]::DateTime, $false)
    $NewClass.Properties["ExtensionID"].Qualifiers.Add("Key", $true)
    $NewClass.Properties["OSUser"].Qualifiers.Add("Key", $true)
    $NewClass.Put() | Out-Null
}
 

# Write-CmTraceLog
function Write-CmTraceLog {
<#
.SYNOPSIS
    This function writes information to a log file in cmtrace format.
.DESCRIPTION
    The function writes information to a log file in cmtrace format.
.PARAMETER LogMessage
    The message to be written to the log file.
.PARAMETER LogFullPath
    The name of the log file to write to - the full path. The path to the file must already exist, the file name doesn't have to exist but the file does need to end in ".log".
.PARAMETER MessageType
    One of "Informational","Warning","Error", or "None". Cmtrace will highlight lines appropriately.
.PARAMETER UtcTime
    If this switch is used then the log will write in UTC time rather than local.
.PARAMETER Component
    The component is required for proper highlighting - if one is not passed in a "default" one will be used.
.PARAMETER Thread
    The thread will be logged as well - if one is passed in that will be used otherwise the current "PID" will be.
.PARAMETER Source
    The source can be passed in as well if desired; in this script's case, that should be the name of the file being run.
.EXAMPLE
    Write-CmTraceLog -LogMessage "Write this message to the log." -LogFullPath "c:\someFolder\MyLogFile.log";
    This will write "Write this message to the log." to a log file named "MyLogFile.log" in the path "c:\someFolder\".
.EXAMPLE
    Write-CmTraceLog -LogMessage "Write this message to the log." -LogFullPath "c:\someFolder\MyLogFile.log" -MessageType Error;
    This will write "Write this message to the log." to a log file named "MyLogFile.log" in the path "c:\someFolder\" and will be highlighted red by cmtrace.
.NOTES
    NAME: Write-CmTraceLog
    HISTORY:
        Date                Author                                         Notes:
        02/08/2019          Benjamin Reynolds (breynol@microsoft.com)      Initial Creation.
        03/16/2021          Benjamin Reynolds (breynol@microsoft.com)      Updated to allow writing to host only and logic to do nothing if the path is empty (and writetohost is false).
        03/24/2021          Benjamin Reynolds (breynol@microsoft.com)      Removed WriteToHost switch and relying on "Verbose" instead.

    NOTES:
        The function doesn't test for the existence of the log file, that should be done within the calling script.
#>
    [cmdletbinding(PositionalBinding=$false)]
    param (
        [Parameter(Mandatory=$true)][ValidateScript({$PSItem.Length -lt 4096})][string]$LogMessage
       ,[Parameter(Mandatory=$true)][AllowNull()][AllowEmptyString()][string]$LogFullPath
       ,[Parameter(Mandatory=$false)][ValidateSet("Informational","Warning","Error","None")][string]$MessageType = "Informational"
       ,[Parameter(Mandatory=$false)][switch]$UtcTime
       ,[Parameter(Mandatory=$false)][string]$Component = "default" # This is required for CMTrace to highlight correctly
       ,[Parameter(Mandatory=$false)][int]$Thread
       ,[Parameter(Mandatory=$false)][string]$Source = ""
    )

    [bool]$isVerbose = $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true;
    
    if ([String]::IsNullOrWhiteSpace($LogFullPath) -and -Not $isVerbose)
    {
        return;
    }

    if ($UtcTime) {
        $CurDate = (Get-Date).ToUniversalTime();
        [string]$Offset = "+000";
    }
    else {
        $CurDate = Get-Date;
        [string]$Offset = (Get-TimeZone).BaseUtcOffset.TotalMinutes;
    }

    ## Write to Host if switch set:
    if ($isVerbose) {
        Write-Verbose "$($CurDate.ToString("yyyy-MM-ddTHH:mm:ss.fff")) : $LogMessage";
        if ([String]::IsNullOrWhiteSpace($LogFullPath)) {return;}
    }
    
    [string]$date = $CurDate.ToString("MM-dd-yyyy");
    [string]$time = $CurDate.ToString("hh:mm:ss.fff")+$Offset;
    
    [string]$LogType = switch ($MessageType) {
                           "Informational" {"1";break;}
                           "Warning" {"2";break;}
                           "Error" {"3";break;}
                           default {"";break;}
                       };

    if (-not $Thread) {
        [int]$Thread = $PID;
    }

    ## Format the message for CMTrace type log line:
     # log.h says this should do this, but we'll just leave carriage returns/new lines alone (which seems like what LogParser.cs does):
     #$LogEntry = "<![LOG[$($LogMessage.Replace("\r\n","~").Replace("\r","~").Replace("\n","~"))]LOG]!><time=""$time"" date=""$date"" component=""$Component"" context="""" type=""$LogType"" thread=""$Thread"" file=""$Source"">";
    $LogEntry = "<![LOG[$LogMessage]LOG]!><time=""$time"" date=""$date"" component=""$Component"" context="""" type=""$LogType"" thread=""$Thread"" file=""$Source"">";

    ## Write to the log:
    Out-File -FilePath $LogFullPath -InputObject $LogEntry -Append -Encoding default;

} # End: Write-CmTraceLog

#
# Process a browser
#
function ProcessBrowser
{
   Param(
      [Parameter(Mandatory)][Browser] $browser
   )
   
   if ($browser -eq [Browser]::Chrome -or $browser -eq [Browser]::Edge)
   {
      # Chromium-based browsers
      Write-CmTraceLog -LogMessage "Looking for $browser" -LogFullPath $LogFullPath -Component "$WMIClass.ps1" -Verbose:$isVerbose;
      # Get the user data directory. If it doesn't exist, we cannot proceed.
      $userDataPath = GetChromiumUserDataPath $browser
      if ($userDataPath -eq $null)
      {
         return
      }

      # Get user info about the profiles
      $profiles = GetProfilesChromium $userDataPath

      # Iterate over the profiles to get info on the extensions
      foreach ($profileDir in $profiles.keys)
      {
         $profilePath = $userDataPath + "\" + $profileDir

         # Check if the user profile directory exists
         if (-not (Test-Path $profilePath))
         {
            continue
         }

         # Get extension info...
         $extensionInfo = GetExtensionInfoFromProfileChromium $profilePath

         Write-CmTraceLog -LogMessage "Info $extensionInfo" -LogFullPath $LogFullPath -Component "$WMIClass.ps1" -Verbose:$isVerbose;
         # ...and write it to stdout
         PrintExtensionInfo "$browser" $profileDir $profiles $extensionInfo
      }
   }
   elseif ($browser -eq [Browser]::Firefox)
   {
      # Firefox
        Write-CmTraceLog -LogMessage "$browser Info" -LogFullPath $LogFullPath -Component "$WMIClass.ps1" -Verbose:$isVerbose;
      # Get the profiles' parent directory
      $profilesPath = GetFirefoxProfilesPath

      # Process each profile
      foreach ($profileDirObject in Get-ChildItem -Path $profilesPath -Directory)
      {
         # Get user info
         $profiles = GetProfileUserInfoFirefox $profileDirObject.Name $profileDirObject.FullName

         # Get extension info...
         $extensionInfo = GetExtensionInfoFromProfileFirefox $profileDirObject.FullName

         # ...and write it to stdout
 

         PrintExtensionInfo "$browser" $profileDirObject.Name $profiles $extensionInfo
      }
   }
   else
   {
      Write-Error "Invalid browser: $browser"
      Write-CmTraceLog -LogMessage "$browser extensions not found" -LogFullPath $LogFullPath -Component "$WMIClass.ps1" -Verbose:$isVerbose;
      return
   }
}

#
# Print collected extension info
#
function PrintExtensionInfo
{
   Param(
      [Parameter(Mandatory)][string] $browserName,
      [Parameter(Mandatory)][string] $profileDir,
      [Parameter(Mandatory)][hashtable] $profiles,
      [Parameter(Mandatory)][hashtable] $extensionInfo
   )

   # Field names
   $osUserNameField                    = "OsUser"
   $browserNameField                   = "Browser"
   $profileDirField                    = "ProfileDir"
   $profileNameField                   = "ProfileName"
   $profileGaiaNameField               = "ProfileGaiaName"
   $profileUserNameField               = "ProfileUserName"
   $extensionIdField                   = "ExtensionId"
   $extensionNameField                 = "ExtensionName"
   $extensionVersionField              = "ExtensionVersion"
   $extensionFromWebstoreField         = "ExtensionFromWebstore"
   $extensionInstalledByDefaultField   = "ExtensionInstalledByDefault"
   $extensionStateField                = "ExtensionState"
   $extensionInstallTimeField          = "ExtensionInstallTime"

   # Field data
   $userName         = $env:USERNAME
   $profileName      = $profiles[$profileDir].profileName
   $profileGaiaName  = $profiles[$profileDir].gaiaName
   $profileUserName  = $profiles[$profileDir].userName

   # Process each extension
   foreach ($extensionId in $extensionInfo.keys)
   {
      $extensionName                = $extensionInfo[$extensionId].name
      $extensionVersion             = $extensionInfo[$extensionId].version
      $extensionFromWebstore        = $extensionInfo[$extensionId].fromWebstore
      $extensionInstalledByDefault  = $extensionInfo[$extensionId].installedByDefault
      $extensionState               = $extensionInfo[$extensionId].state
      $extensionInstallTime         = $extensionInfo[$extensionId].installTime

      $output = "$osUserNameField=`"$userName`" $browserNameField=`"$browserName`" $profileDirField=`"$profileDir`" $profileNameField=`"$profileName`" $profileGaiaNameField=`"$profileGaiaName`" $profileUserNameField=`"$profileUserName`" " + `
                "$extensionIdField=`"$extensionId`" $extensionNameField=`"$extensionName`" $extensionVersionField=`"$extensionVersion`" $extensionFromWebstoreField=`"$extensionFromWebstore`" $extensionStateField=`"$extensionState`" " + `
                "$extensionInstallTimeField=`"$extensionInstallTime`" $extensionInstalledByDefaultField=`"$extensionInstalledByDefault`""

      #Write-Output $output
      Write-CmTraceLog -LogMessage "$output" -LogFullPath $LogFullPath -Component "$WMIClass.ps1" -Verbose:$isVerbose;

      #  Save to WMI
                (Set-WmiInstance -Namespace root/$WMINamespace -Class $WMIClass -Arguments @{
                OSUser=$userName;
                Browser=$browserName;
                ProfileDir=$profileDir;
                ProfileName=$profileName;
                ProfileGaiaName=$profileGaiaName;
                ProfileUserName=$profileUserName;
                ExtensionID=$extensionId;
                ExtensionName=$extensionName;
                ExtensionVersion=$extensionVersion;
                ExtensionFromWebStore=$extensionFromWebstore;
                ExtensionState=$extensionState;
                ExtensionInstallTime=$extensionInstallTime;
                ExtensionInstalledByDefault=$extensionInstalledByDefault;
                ScriptLastRan=$ScriptRan}) | Out-Null
   }
}


#
# Get extension JSON settings from a Chromium profile's (secure) preferences
#
function GetExtensionJsonFromPreferencesChromium
{
   Param(
      [Parameter(Mandatory)][string] $preferencesFile
   )

   # Check if the secure preferences file exists
   if (-not (Test-Path $preferencesFile -PathType Leaf))
   {
      return
   }

   # Read the preferences file & convert to JSON
   $preferencesJson = Get-Content -Path $preferencesFile -Encoding UTF8 | ConvertFrom-Json

   # The extensions are children of extensions > settings
   return $preferencesJson.extensions.settings
}

#
# Get extension JSON settings from a Firefox profile's preferences
#
function GetExtensionJsonFromPreferencesFirefox
{
   Param(
      [Parameter(Mandatory)][string] $preferencesFile
   )

   # Check if the secure preferences file exists
   if (-not (Test-Path $preferencesFile -PathType Leaf))
   {
      return
   }

   # Read the preferences file & convert to JSON
   $preferencesJson = Get-Content -Path $preferencesFile -Encoding UTF8 | ConvertFrom-Json

   # The extensions are children of extensions > settings
   return $preferencesJson.addons
}

#
# Get extension information from a Chromium-based profile
#
function GetExtensionInfoFromProfileChromium
{
   Param(
      [Parameter(Mandatory)][string] $profilePath
   )

   # Out variable
   $extensionsMap = @{}

   # Try to get extension info from secure preferences
   $extensionsJson = GetExtensionJsonFromPreferencesChromium ($profilePath + "\Secure Preferences")
   if ($extensionsJson -eq $null)
   {
      # Try regular preferences instead
      $extensionsJson = GetExtensionJsonFromPreferencesChromium ($profilePath + "\Preferences")
      if ($extensionsJson -eq $null)
      {
         return $extensionsMap
      }
   }

   # The extensions are children of extensions > settings
   $extensionIds   = $extensionsJson.psobject.properties.name

   # Extract properties of each extension
   foreach ($extensionId in $extensionIds)
   {
      # Ignore extensions installed by default (that also don't show up in the extensions UI)
      # $installedByDefault = $extensionsJson.$extensionId.was_installed_by_default
      # if ($installedByDefault -eq $true)
      # {
      #    continue
      # }

      # Ignore extensions located outside the user data directory (e.g., extensions that ship with the browser)
      # Location values seen:
      #    1: Profile (user data)
      #    5: Install directory (program files)
      #   10: Profile (user data) [not sure about the difference to 1]
      $location = $extensionsJson.$extensionId.location
      if ($location -eq 5)
      {
         continue
      }

      # Ignore extensions whose directory does not exist
      $extensionPath = $profilePath + "\Extensions\" + $extensionId
      if (-not (Test-Path $extensionPath))
      {
         continue
      }

      # Last install time
      $updateTimeMs = ConvertChromeTimestampToEpochMs $extensionsJson.$extensionId.install_time

      # Manifest
      $manifestJson = $extensionsJson.$extensionId.manifest
      if ($manifestJson -eq $null)
      {
         # Ignore entries without a manifest
         continue
      }
      $name    = $manifestJson.name
      $version = $manifestJson.version

      # Build a hashtable with the properties of this extension
      $extensionMap  =
      @{
         name                 = $name                                                  # Extension name
         version              = $version                                               # Extension version
         fromWebstore         = $extensionsJson.$extensionId.from_webstore             # Was the extension installed from the Chrome Web Store?
         installedByDefault   = $extensionsJson.$extensionId.was_installed_by_default  # Was the extension installed by default?
         state                = $extensionsJson.$extensionId.state                     # Extension state (1 = enabled)
         installTime          = $updateTimeMs                                          # Timestamp of the last installation (= update) as Unix epoch in ms
      }

      # Add this extension to the list of extensions
      $extensionsMap[$extensionId] = $extensionMap;
   }

   # Return the list of extensions
   return $extensionsMap
}

#
# Get extension information from a Firefox profile
#
function GetExtensionInfoFromProfileFirefox
{
   Param(
      [Parameter(Mandatory)][string] $profilePath
   )

   # Out variable
   $extensionsMap = @{}

   # Try to get extension info from extensions.json
   $extensionsJson = GetExtensionJsonFromPreferencesFirefox ($profilePath + "\extensions.json")
   if ($extensionsJson -eq $null)
   {
      return $extensionsMap
   }

   # Extract properties of each extension
   foreach ($extensionJson in $extensionsJson)
   {
      # Ignore addons outside the profile
      if ($extensionJson.location -ne "app-profile")
      {
         continue
      }
      # Ignore addons that are not extensions
      if ($extensionJson.type -ne "extension")
      {
         continue
      }

      # State (enabled/disabled)
      $state = "0"
      if ($extensionJson.active -eq $true)
      {
         $state = "1"
      }

      # Default locale
      $defaultLocale = $extensionJson.defaultLocale
      if ($defaultLocale -eq $null)
      {
         # Ignore entries without a manifest
         continue
      }
      $name = $defaultLocale.name

      # Installation source: Firefox Addons?
      # sourceURI must be https://addons.cdn.mozilla.net or https://addons.mozilla.org
      $fromFirefoxAddons = $false
      if ($extensionJson.sourceURI -match "^http(s)?://addons\.(cdn\.)?mozilla\.")
      {
         $fromFirefoxAddons = $true
      }

      # Build a hashtable with the properties of this extension
      $extensionMap  =
      @{
         name                 = $name                       # Extension name
         version              = $extensionJson.version      # Extension version
         fromWebstore         = $fromFirefoxAddons          # Was the extension installed from Firefox Addons?
         state                = $state                      # Extension state (1 = enabled)
         installTime          = $extensionJson.updateDate   # Last update timestamp as Unix epoch in ms
      }

      # Add this extension to the list of extensions
      $extensionsMap[$extensionJson.id] = $extensionMap;
   }

   # Return the list of extensions
   return $extensionsMap
}

#
# Convert a Chrome timestamp to Unix epoch milliseconds
#
#    Chrome time format: Windows FILETIME / 10 (microsecond intervals since 1601-01-01)
#
function ConvertChromeTimestampToEpochMs
{
   Param(
      [Parameter()][long] $chromeTimestamp
   )

   if ($chromeTimestamp -eq $null)
   {
      return
   }

   $filetime = $chromeTimestamp * 10
   $datetime = [datetime]::FromFileTime($filetime)
   return ([DateTimeOffset]$datetime).ToUnixTimeMilliseconds()
}

#
# Chrome: get the profiles (along with user info)
#
function GetProfilesChromium
{
   Param(
      [Parameter(Mandatory)][string] $userDataPath
   )

   # Out variable
   $profilesMap = @{}

   # Build the path to the local state file
   $localStatePath = $userDataPath + "\Local State"

   # Check if the local state file exists
   if (-not (Test-Path $localStatePath -PathType Leaf))
   {
      return $profilesMap
   }

   # Read the local state file & convert to JSON
   $localStateJson = Get-Content -Path $localStatePath -Encoding UTF8 | ConvertFrom-Json

   # The profiles are children of profile > info_cache
   $infoCacheJson = $localStateJson.profile.info_cache
   $profileDirs   = $infoCacheJson.psobject.properties.name

   # Extract properties of each profile
   foreach ($profileDir in $profileDirs)
   {
      # Build a hashtable with the properties of this profile
      $profileMap  =
      @{
         profileName = $infoCacheJson.$profileDir.name        # Name of the browser profile, e.g.: Person 1
         gaiaName    = $infoCacheJson.$profileDir.gaia_name   # Name of the profile's user, e.g.: John Doe
         userName    = $infoCacheJson.$profileDir.user_name   # Email of the profile's user, e.g.: john@domain.com
      }

      # Add this profile to the list of profiles
      $profilesMap[$profileDir] = $profileMap;
   }

   # Return the list of profiles
   return $profilesMap
}

#
# Firefox: get user info for a profile
#
function GetProfileUserInfoFirefox
{
   Param(
      [Parameter(Mandatory)][string] $profileDir,
      [Parameter(Mandatory)][string] $profilePath
   )

   # Out variables
   $userEmail = ""
   $profilesMap = @{}

   # Extract the profile name from the profile directory (everything after the first dot)
   $profileName = ""
   if ($profileDir -match "[^\.]+\.(.+)")
   {
      $profileName = $Matches[1]
   }

   # Build the path to the file signedInUser.json
   $signedInUserPath = $profilePath + "\signedInUser.json"

   # Check if the signedInUser.json file exists
   if (Test-Path $signedInUserPath -PathType Leaf)
   {
      # Read the signedInUser.json file & convert to JSON
      $signedInUserJson = Get-Content -Path $signedInUserPath -Encoding UTF8 | ConvertFrom-Json

      # User Info is in accountData
      $accountData = $signedInUserJson.accountData
      if ($accountData -ne $null)
      {
         $userEmail = $accountData.email
      }
   }

   # Build a hashtable with the properties of this profile
   $profileMap  =
   @{
      userName    = $userEmail         # Email of the profile's user, e.g.: john@domain.com
      profileName = $profileName       # Name of the browser profile, e.g.: Person 1
   }

   # Add this profile to the list of profiles
   $profilesMap[$profileDir] = $profileMap;

   # Return the list of profiles
   return $profilesMap
}

#
# Get the Chrome user data directory
#
function GetChromiumUserDataPath
{
   Param(
      [Parameter(Mandatory)][Browser] $browser
   )

   $userDataPath = ""
   
   # Build the path to the user data directory
   if ($browser -eq [Browser]::Chrome)
   {
      $userDataPath = $env:LOCALAPPDATA + "\Google\Chrome\User Data"
   }
   elseif ($browser -eq [Browser]::Edge)
   {
      $userDataPath = $env:LOCALAPPDATA + "\Microsoft\Edge\User Data"
   }
   else
   {
      Write-Error "Invalid browser: $browser"
      return
   }

   # Check if the Chrome data directory exists
   if (Test-Path $userDataPath)
   {
      return $userDataPath
   }
   else
   {
      return
   }
}

#
# Get the Firefox profiles directory
#
function GetFirefoxProfilesPath
{
   $profilesPath = ""
   
   # Build the path to the profiles directory
   $profilesPath = $env:APPDATA + "\Mozilla\Firefox\Profiles"

   # Check if the profiles directory exists
   if (Test-Path $profilesPath)
   {
      return $profilesPath
   }
   else
   {
      return
   }
}

#
# Script start
#
$ErrorActionPreference = 'Continue'
try
{
  ## Start of Script
  if (Test-Path $LogFullPath) {Remove-Item $LogFullPath}
  [string]$scriptRunTime = (Get-Date).ToUniversalTime().ToString("MM/dd/yyyy HH:mm:ss");
  Write-CmTraceLog -LogMessage "***** Starting Script ***** ScriptVersion $ScriptVersion" -LogFullPath $LogFullPath -Component "$WMIClass.ps1" -Verbose:$isVerbose;
  

  #region Ensure Class Exists
  try
  {
    ## Make sure the class exists:
    if (-Not (Get-CimClass -Namespace "root\$WMINamespace" -ClassName $WMIClass -ErrorAction SilentlyContinue))
    {
      Write-CmTraceLog -LogMessage "Creating the class '$WMIClass' because it doesn't exist yet." -LogFullPath $LogFullPath -Component "$WMIClass.ps1" -Verbose:$isVerbose;
      $null = CreateClass;
    }
    else
    {
      Write-CmTraceLog -LogMessage "Class '$WMIClass' already exists" -LogFullPath $LogFullPath -Component "$WMIClass.ps1" -Verbose:$isVerbose;
    }
  }
  catch
  {
    Write-CmTraceLog -LogMessage "Error caught trying to create the class; Error Details:`r`n$(Out-String -InputObject $PSItem)" -LogFullPath $LogFullPath -MessageType Error -Component "$WMIClass.ps1" -Verbose:$isVerbose;
    throw "Error Trying to create the WMI class";
  }
  #endregion
  
  #region Delete any existing instances for the user
  ## Need to delete instances for the user:
  try
  {
    Write-CmTraceLog -LogMessage "Deleting any current instances..." -LogFullPath $LogFullPath -Component "$WMIClass.ps1" -Verbose:$isVerbose;
    $userName         = $env:USERNAME
    Remove-CimInstance -Namespace "root\$WMINamespace" -Query "SELECT * FROM $WMIClass WHERE OSUser = '$userName'";
  }
  catch
  {
    Write-CmTraceLog -LogMessage "Error Caught trying to delete current instances. Error Information:`r`n$(Out-String -InputObject $PSItem)" -LogFullPath $LogFullPath -MessageType Error -Component "$WMIClass.ps1" -Verbose:$isVerbose;
    throw "Error trying to delete current instances";
  }
  #endregion

ProcessBrowser Chrome
ProcessBrowser Edge
ProcessBrowser Firefox
        Write-CmTraceLog -LogMessage "Browser Extension Information Gathering Completed" -LogFullPath $LogFullPath -Component "$WMIClass.ps1" -Verbose:$isVerbose;
write-host 'done'

}
catch {}