diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e43b0f9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.DS_Store
diff --git a/DNS Detection Script.ps1 b/DNS Detection Script.ps1
new file mode 100755
index 0000000..341c307
--- /dev/null
+++ b/DNS Detection Script.ps1
@@ -0,0 +1,28 @@
+'''
+
+Created: 2025-03-05
+Modified: 2025-03-05
+Description: This script checks to ensure the DNS servers are set to the DNS servers that filter malware
+
+'''
+
+# Get all network adapters
+$adapters = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' }
+
+# Check DNS server settings
+foreach ($adapter in $adapters) {
+ $dnsServers = Get-DnsClientServerAddress -InterfaceIndex $adapter.InterfaceIndex
+ $staticDnsServers = $dnsServers | Where-Object { $_.ServerAddresses -contains '9.9.9.9' -or $_.ServerAddresses -contains '149.112.112.11' -or $_.ServerAddresses -contains '1.1.1.3' -or $_.ServerAddresses -contains '1.0.0.3' }
+
+ if ($staticDnsServers.Count -eq 0) {
+ Write-Host "Adapter $($adapter.Name) does not have the correct DNS servers configured."
+ $compliance = "false"
+ }
+ else {
+ Write-Host "Adapter $($adapter.Name) has the correct DNS servers configured."
+ $compliance = "true"
+ }
+}
+
+$hash = @{ Compliant = $compliance}
+return $hash | ConvertTo-Json -Compress
\ No newline at end of file
diff --git a/DirectoryListing.ps1 b/DirectoryListing.ps1
new file mode 100755
index 0000000..d8cfee8
--- /dev/null
+++ b/DirectoryListing.ps1
@@ -0,0 +1,209 @@
+$root = 'E:\Sync Method\Corporate\Zefiro Methane Corp. - Documents'
+$outputDirectory = 'C:\Users\MattSpeer\OneDrive - Add Source Group\Documents\1- Projects\Zefiro - Data Migration'
+$textFile = Join-Path $outputDirectory 'Zefiro-Methane-Corp-Documents-tree.txt'
+$htmlFile = Join-Path $outputDirectory 'Zefiro-Methane-Corp-Documents-tree.html'
+
+if (-not (Test-Path -LiteralPath $root -PathType Container)) {
+ throw "Folder not found: $root"
+}
+
+New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
+
+$script:textLines = [System.Collections.Generic.List[string]]::new()
+$script:html = [System.Text.StringBuilder]::new()
+$script:scanErrors = 0
+
+function ConvertTo-HtmlEncoded {
+ param([string]$Text)
+ return [System.Net.WebUtility]::HtmlEncode($Text)
+}
+
+function Add-TreeItems {
+ param(
+ [string]$Path,
+ [string]$Prefix
+ )
+
+ try {
+ $items = @(
+ Get-ChildItem -LiteralPath $Path -Force -ErrorAction Stop |
+ Sort-Object @{ Expression = { if ($_.PSIsContainer) { 0 } else { 1 } } }, Name
+ )
+ }
+ catch {
+ $script:scanErrors++
+ [void]$script:textLines.Add("$Prefix[Unable to read: $($_.Exception.Message)]")
+ [void]$script:html.AppendLine(
+ "
Unable to read: $(ConvertTo-HtmlEncoded $_.Exception.Message) "
+ )
+ return
+ }
+
+ for ($index = 0; $index -lt $items.Count; $index++) {
+ $item = $items[$index]
+ $isLast = $index -eq ($items.Count - 1)
+ $connector = if ($isLast) { '\-- ' } else { '|-- ' }
+ $nextPrefix = $Prefix + $(if ($isLast) { ' ' } else { '| ' })
+
+ $nameHtml = ConvertTo-HtmlEncoded $item.Name
+ $pathHtml = ConvertTo-HtmlEncoded $item.FullName
+ $isLink = -not [string]::IsNullOrWhiteSpace([string]$item.LinkType)
+
+ if ($item.PSIsContainer -and -not $isLink) {
+ [void]$script:textLines.Add("$Prefix$connector$($item.Name)")
+ [void]$script:html.AppendLine(
+ "📁 $nameHtml "
+ )
+
+ Add-TreeItems -Path $item.FullName -Prefix $nextPrefix
+
+ [void]$script:html.AppendLine(' ')
+ }
+ elseif ($item.PSIsContainer -and $isLink) {
+ $target = @($item.Target) -join ', '
+ [void]$script:textLines.Add(
+ "$Prefix$connector$($item.Name) [Link → $target]"
+ )
+ [void]$script:html.AppendLine(
+ "🔗 $nameHtml → $(ConvertTo-HtmlEncoded $target) "
+ )
+ }
+ else {
+ [void]$script:textLines.Add("$Prefix$connector$($item.Name)")
+ [void]$script:html.AppendLine(
+ "📄 $nameHtml "
+ )
+ }
+ }
+}
+
+$rootNameHtml = ConvertTo-HtmlEncoded (Split-Path -Path $root -Leaf)
+$rootPathHtml = ConvertTo-HtmlEncoded $root
+$generated = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
+
+[void]$script:textLines.Add($root)
+
+[void]$script:html.AppendLine(@"
+
+
+
+
+
+Team Site Documents Directory
+
+
+
+Team Site – Documents
+
+Generated $generated
+Root: $rootPathHtml
+
+
+
+Expand all
+Collapse all
+
+
+
+
+📁 $rootNameHtml
+
+"@)
+
+Add-TreeItems -Path $root -Prefix ''
+
+[void]$script:html.AppendLine(@"
+
+
+
+
+
+
+
+"@)
+
+$script:textLines | Set-Content -LiteralPath $textFile -Encoding UTF8
+$script:html.ToString() | Set-Content -LiteralPath $htmlFile -Encoding UTF8
+
+Write-Host "Text report: $textFile"
+Write-Host "HTML report: $htmlFile"
+Write-Host "Unreadable folders: $script:scanErrors"
+
+Start-Process $htmlFile
\ No newline at end of file
diff --git a/Download-All-SharePoint-Files.ps1 b/Download-All-SharePoint-Files.ps1
new file mode 100755
index 0000000..0305705
--- /dev/null
+++ b/Download-All-SharePoint-Files.ps1
@@ -0,0 +1,112 @@
+$SiteUrl = "https://zefiromethane.sharepoint.com/sites/contentTypeHub"
+$Destination = "F:\TeamSite"
+
+# Opens a Microsoft sign-in window.
+Connect-MgGraph -Scopes "Sites.Read.All" -NoWelcome
+
+function Get-GraphCollection {
+ param([string]$Uri)
+
+ do {
+ $response = Invoke-MgGraphRequest -Method GET -Uri $Uri
+
+ foreach ($item in @($response.value)) {
+ Write-Output $item
+ }
+
+ $Uri = $response.'@odata.nextLink'
+ }
+ while ($Uri)
+}
+
+function Copy-GraphFolder {
+ param(
+ [string]$DriveId,
+ [string]$ChildrenUri,
+ [string]$LocalFolder
+ )
+
+ New-Item -ItemType Directory -Path $LocalFolder -Force |
+ Out-Null
+
+ foreach ($item in Get-GraphCollection -Uri $ChildrenUri) {
+ $localPath = Join-Path $LocalFolder $item.name
+ $escapedDriveId = [Uri]::EscapeDataString($DriveId)
+ $escapedItemId = [Uri]::EscapeDataString([string]$item.id)
+
+ if ($null -ne $item.folder -or $null -ne $item.package) {
+ # Create the folder, including empty folders.
+ New-Item -ItemType Directory -Path $localPath -Force |
+ Out-Null
+
+ $childUri = `
+ "https://graph.microsoft.com/v1.0/drives/" +
+ "$escapedDriveId/items/$escapedItemId/children"
+
+ Copy-GraphFolder `
+ -DriveId $DriveId `
+ -ChildrenUri $childUri `
+ -LocalFolder $localPath
+ }
+ elseif ($null -ne $item.file) {
+ if (Test-Path -LiteralPath $localPath) {
+ Write-Host "Skipping existing file: $localPath"
+ continue
+ }
+
+ Write-Host "Downloading: $localPath"
+
+ $contentUri = `
+ "https://graph.microsoft.com/v1.0/drives/" +
+ "$escapedDriveId/items/$escapedItemId/content"
+
+ Invoke-MgGraphRequest `
+ -Method GET `
+ -Uri $contentUri `
+ -OutputFilePath $localPath
+ }
+ else {
+ Write-Warning "Unsupported item skipped: $($item.name)"
+ }
+ }
+}
+
+# Resolve the SharePoint site from its URL.
+$parsedSiteUrl = [Uri]$SiteUrl
+$sitePath = $parsedSiteUrl.AbsolutePath.TrimEnd("/")
+
+if ([string]::IsNullOrWhiteSpace($sitePath)) {
+ $siteLookupUri = `
+ "https://graph.microsoft.com/v1.0/sites/$($parsedSiteUrl.Host)"
+}
+else {
+ $siteLookupUri = `
+ "https://graph.microsoft.com/v1.0/sites/" +
+ "$($parsedSiteUrl.Host):$sitePath"
+}
+
+$site = Invoke-MgGraphRequest -Method GET -Uri $siteLookupUri
+
+New-Item -ItemType Directory -Path $Destination -Force |
+ Out-Null
+
+# A Graph "drive" represents a SharePoint document library.
+$drivesUri = "https://graph.microsoft.com/v1.0/sites/$($site.id)/drives"
+
+foreach ($drive in Get-GraphCollection -Uri $drivesUri) {
+ $localLibrary = Join-Path $Destination $drive.name
+ $escapedDriveId = [Uri]::EscapeDataString([string]$drive.id)
+
+ Write-Host "Downloading library: $($drive.name)"
+
+ $rootChildrenUri = `
+ "https://graph.microsoft.com/v1.0/drives/" +
+ "$escapedDriveId/root/children"
+
+ Copy-GraphFolder `
+ -DriveId $drive.id `
+ -ChildrenUri $rootChildrenUri `
+ -LocalFolder $localLibrary
+}
+
+Disconnect-MgGraph
\ No newline at end of file
diff --git a/Export-DocuSignCompletedAgreements.ps1 b/Export-DocuSignCompletedAgreements.ps1
new file mode 100644
index 0000000..5abb328
--- /dev/null
+++ b/Export-DocuSignCompletedAgreements.ps1
@@ -0,0 +1,378 @@
+<#
+.SYNOPSIS
+Exports completed DocuSign envelopes, including the Certificate of Completion.
+
+.DESCRIPTION
+Uses the DocuSign eSignature REST API to:
+ 1. Authenticate with JWT.
+ 2. List completed envelopes in a date range.
+ 3. Download each envelope as either an archive ZIP or combined PDF.
+ 4. Write a CSV export log.
+
+Prerequisites:
+ - PowerShell 7 recommended.
+ - A DocuSign app/integration key.
+ - RSA private key PEM for the integration.
+ - Consent granted for scopes: signature impersonation.
+ - The impersonated user must have access to the target envelopes.
+
+Consent URL format:
+ Production:
+ https://account.docusign.com/oauth/auth?response_type=code&scope=signature%20impersonation&client_id=INTEGRATION_KEY&redirect_uri=https://www.docusign.com
+
+ Demo:
+ https://account-d.docusign.com/oauth/auth?response_type=code&scope=signature%20impersonation&client_id=INTEGRATION_KEY&redirect_uri=https://www.docusign.com
+
+.EXAMPLE
+.\Export-DocuSignCompletedAgreements.ps1 `
+ -IntegrationKey "00000000-0000-0000-0000-000000000000" `
+ -UserId "11111111-1111-1111-1111-111111111111" `
+ -AccountId "22222222-2222-2222-2222-222222222222" `
+ -PrivateKeyPath "C:\Keys\docusign_private.key" `
+ -FromDate "2020-01-01" `
+ -OutputRoot "D:\DocuSignExport" `
+ -OutputFormat ArchiveZip
+#>
+
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory = $true)]
+ [string]$IntegrationKey,
+
+ [Parameter(Mandatory = $true)]
+ [string]$UserId,
+
+ [Parameter(Mandatory = $true)]
+ [string]$AccountId,
+
+ [Parameter(Mandatory = $true)]
+ [string]$PrivateKeyPath,
+
+ [Parameter(Mandatory = $true)]
+ [datetime]$FromDate,
+
+ [Parameter(Mandatory = $false)]
+ [datetime]$ToDate = (Get-Date),
+
+ [Parameter(Mandatory = $true)]
+ [string]$OutputRoot,
+
+ [Parameter(Mandatory = $false)]
+ [ValidateSet("Production", "Demo")]
+ [string]$Environment = "Production",
+
+ [Parameter(Mandatory = $false)]
+ [ValidateSet("ArchiveZip", "CombinedPdf")]
+ [string]$OutputFormat = "ArchiveZip",
+
+ [Parameter(Mandatory = $false)]
+ [int]$PageSize = 1000,
+
+ [Parameter(Mandatory = $false)]
+ [int]$RetryCount = 4
+)
+
+$ErrorActionPreference = "Stop"
+
+function ConvertTo-Base64Url {
+ param([byte[]]$Bytes)
+ [Convert]::ToBase64String($Bytes).TrimEnd("=").Replace("+", "-").Replace("/", "_")
+}
+
+function ConvertFrom-Base64UrlString {
+ param([string]$Value)
+ $base64 = $Value.Replace("-", "+").Replace("_", "/")
+ switch ($base64.Length % 4) {
+ 2 { $base64 += "==" }
+ 3 { $base64 += "=" }
+ }
+ [Convert]::FromBase64String($base64)
+}
+
+function Import-RsaPrivateKey {
+ param([string]$Path)
+
+ $pem = Get-Content -Path $Path -Raw
+ $rsa = [System.Security.Cryptography.RSA]::Create()
+
+ if ($pem -match "-----BEGIN RSA PRIVATE KEY-----") {
+ $body = $pem -replace "-----BEGIN RSA PRIVATE KEY-----", "" -replace "-----END RSA PRIVATE KEY-----", "" -replace "\s", ""
+ $bytes = [Convert]::FromBase64String($body)
+ [void]$rsa.ImportRSAPrivateKey($bytes, [ref]0)
+ return $rsa
+ }
+
+ if ($pem -match "-----BEGIN PRIVATE KEY-----") {
+ $body = $pem -replace "-----BEGIN PRIVATE KEY-----", "" -replace "-----END PRIVATE KEY-----", "" -replace "\s", ""
+ $bytes = [Convert]::FromBase64String($body)
+ [void]$rsa.ImportPkcs8PrivateKey($bytes, [ref]0)
+ return $rsa
+ }
+
+ throw "Private key must be PEM formatted with BEGIN RSA PRIVATE KEY or BEGIN PRIVATE KEY."
+}
+
+function New-DocuSignJwtAssertion {
+ param(
+ [string]$IntegrationKey,
+ [string]$UserId,
+ [string]$AuthServer,
+ [string]$PrivateKeyPath
+ )
+
+ $now = [DateTimeOffset]::UtcNow
+ $header = @{
+ alg = "RS256"
+ typ = "JWT"
+ } | ConvertTo-Json -Compress
+
+ $payload = @{
+ iss = $IntegrationKey
+ sub = $UserId
+ aud = $AuthServer
+ iat = $now.ToUnixTimeSeconds()
+ exp = $now.AddMinutes(55).ToUnixTimeSeconds()
+ scope = "signature impersonation"
+ } | ConvertTo-Json -Compress
+
+ $encodedHeader = ConvertTo-Base64Url ([Text.Encoding]::UTF8.GetBytes($header))
+ $encodedPayload = ConvertTo-Base64Url ([Text.Encoding]::UTF8.GetBytes($payload))
+ $unsignedToken = "$encodedHeader.$encodedPayload"
+
+ $rsa = Import-RsaPrivateKey -Path $PrivateKeyPath
+ try {
+ $signature = $rsa.SignData(
+ [Text.Encoding]::UTF8.GetBytes($unsignedToken),
+ [Security.Cryptography.HashAlgorithmName]::SHA256,
+ [Security.Cryptography.RSASignaturePadding]::Pkcs1
+ )
+ }
+ finally {
+ $rsa.Dispose()
+ }
+
+ "$unsignedToken.$(ConvertTo-Base64Url $signature)"
+}
+
+function Invoke-WithRetry {
+ param(
+ [scriptblock]$ScriptBlock,
+ [int]$RetryCount = 4,
+ [string]$ActionName = "DocuSign API request"
+ )
+
+ for ($attempt = 1; $attempt -le ($RetryCount + 1); $attempt++) {
+ try {
+ return & $ScriptBlock
+ }
+ catch {
+ $response = $_.Exception.Response
+ $statusCode = $null
+ if ($response -and $response.StatusCode) {
+ $statusCode = [int]$response.StatusCode
+ }
+
+ $retryable = $statusCode -in @(429, 500, 502, 503, 504)
+ if (-not $retryable -or $attempt -gt $RetryCount) {
+ throw
+ }
+
+ $delaySeconds = [math]::Min(60, [math]::Pow(2, $attempt))
+ Write-Warning "$ActionName failed with HTTP $statusCode. Retrying in $delaySeconds seconds. Attempt $attempt of $RetryCount."
+ Start-Sleep -Seconds $delaySeconds
+ }
+ }
+}
+
+function Get-SafeFileName {
+ param(
+ [string]$Text,
+ [int]$MaxLength = 120
+ )
+
+ $invalid = [Regex]::Escape(([IO.Path]::GetInvalidFileNameChars() -join ""))
+ $safe = [Regex]::Replace($Text, "[$invalid]", "_")
+ $safe = [Regex]::Replace($safe, "\s+", " ").Trim()
+ if ([string]::IsNullOrWhiteSpace($safe)) {
+ $safe = "Envelope"
+ }
+ if ($safe.Length -gt $MaxLength) {
+ $safe = $safe.Substring(0, $MaxLength).Trim()
+ }
+ $safe
+}
+
+function Save-Bytes {
+ param(
+ [string]$Uri,
+ [hashtable]$Headers,
+ [string]$Path,
+ [int]$RetryCount
+ )
+
+ Invoke-WithRetry -RetryCount $RetryCount -ActionName "Download $Path" -ScriptBlock {
+ Invoke-WebRequest -Uri $Uri -Headers $Headers -OutFile $Path
+ } | Out-Null
+}
+
+$authServer = if ($Environment -eq "Demo") { "account-d.docusign.com" } else { "account.docusign.com" }
+$tokenUri = "https://$authServer/oauth/token"
+
+Write-Host "Requesting DocuSign access token..."
+$assertion = New-DocuSignJwtAssertion `
+ -IntegrationKey $IntegrationKey `
+ -UserId $UserId `
+ -AuthServer $authServer `
+ -PrivateKeyPath $PrivateKeyPath
+
+$tokenResponse = Invoke-WithRetry -RetryCount $RetryCount -ActionName "JWT token request" -ScriptBlock {
+ Invoke-RestMethod `
+ -Method Post `
+ -Uri $tokenUri `
+ -ContentType "application/x-www-form-urlencoded" `
+ -Body @{
+ grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer"
+ assertion = $assertion
+ }
+}
+
+$headers = @{
+ Authorization = "Bearer $($tokenResponse.access_token)"
+}
+
+Write-Host "Discovering account base URI..."
+$userInfo = Invoke-WithRetry -RetryCount $RetryCount -ActionName "UserInfo request" -ScriptBlock {
+ Invoke-RestMethod -Method Get -Uri "https://$authServer/oauth/userinfo" -Headers $headers
+}
+
+$account = $userInfo.accounts | Where-Object { $_.account_id -eq $AccountId } | Select-Object -First 1
+if (-not $account) {
+ throw "AccountId '$AccountId' was not found for the impersonated user."
+}
+
+$baseUri = $account.base_uri.TrimEnd("/")
+$apiBase = "$baseUri/restapi/v2.1/accounts/$AccountId"
+
+$downloadFolder = Join-Path $OutputRoot "completed-agreements"
+$logFolder = Join-Path $OutputRoot "logs"
+New-Item -ItemType Directory -Path $downloadFolder -Force | Out-Null
+New-Item -ItemType Directory -Path $logFolder -Force | Out-Null
+
+$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
+$logPath = Join-Path $logFolder "docusign-export-log-$timestamp.csv"
+
+$documentId = if ($OutputFormat -eq "ArchiveZip") { "archive" } else { "combined" }
+$extension = if ($OutputFormat -eq "ArchiveZip") { ".zip" } else { ".pdf" }
+$accept = if ($OutputFormat -eq "ArchiveZip") { "application/zip" } else { "application/pdf" }
+
+$downloadHeaders = @{
+ Authorization = "Bearer $($tokenResponse.access_token)"
+ Accept = $accept
+}
+
+$from = $FromDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
+$to = $ToDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
+
+Write-Host "Finding completed envelopes from $from to $to..."
+$allEnvelopes = New-Object System.Collections.Generic.List[object]
+$startPosition = 0
+
+do {
+ $query = @{
+ from_date = $from
+ to_date = $to
+ status = "completed"
+ count = $PageSize
+ start_position = $startPosition
+ order_by = "completed"
+ order = "asc"
+ }
+
+ $queryString = ($query.GetEnumerator() | ForEach-Object {
+ "{0}={1}" -f [Uri]::EscapeDataString($_.Key), [Uri]::EscapeDataString([string]$_.Value)
+ }) -join "&"
+
+ $uri = "$apiBase/envelopes?$queryString"
+ $page = Invoke-WithRetry -RetryCount $RetryCount -ActionName "Envelope list page starting $startPosition" -ScriptBlock {
+ Invoke-RestMethod -Method Get -Uri $uri -Headers $headers
+ }
+
+ if ($page.envelopes) {
+ foreach ($envelope in $page.envelopes) {
+ $allEnvelopes.Add($envelope)
+ }
+ }
+
+ $returned = if ($page.envelopes) { @($page.envelopes).Count } else { 0 }
+ Write-Host "Found $returned envelopes on page starting at $startPosition."
+
+ $startPosition += $PageSize
+ $totalSetSize = [int]$page.totalSetSize
+} while ($allEnvelopes.Count -lt $totalSetSize -and $returned -gt 0)
+
+Write-Host "Total completed envelopes found: $($allEnvelopes.Count)"
+
+$logRows = New-Object System.Collections.Generic.List[object]
+$counter = 0
+
+foreach ($envelope in $allEnvelopes) {
+ $counter++
+ $envelopeId = $envelope.envelopeId
+ $subject = Get-SafeFileName -Text $envelope.emailSubject
+
+ $completedRaw = if ($envelope.completedDateTime) { $envelope.completedDateTime } else { $envelope.statusChangedDateTime }
+ $completedDate = if ($completedRaw) {
+ ([datetime]$completedRaw).ToString("yyyy-MM-dd")
+ } else {
+ "unknown-date"
+ }
+
+ $fileName = "$completedDate - $subject - $envelopeId$extension"
+ $filePath = Join-Path $downloadFolder $fileName
+ $downloadUri = "$apiBase/envelopes/$envelopeId/documents/$documentId`?certificate=true"
+
+ Write-Progress `
+ -Activity "Downloading completed DocuSign agreements" `
+ -Status "$counter of $($allEnvelopes.Count): $subject" `
+ -PercentComplete (($counter / [math]::Max(1, $allEnvelopes.Count)) * 100)
+
+ $status = "Downloaded"
+ $errorMessage = ""
+
+ try {
+ Save-Bytes -Uri $downloadUri -Headers $downloadHeaders -Path $filePath -RetryCount $RetryCount
+ }
+ catch {
+ $status = "Failed"
+ $errorMessage = $_.Exception.Message
+ Write-Warning "Failed to download envelope $envelopeId`: $errorMessage"
+ }
+
+ $logRows.Add([PSCustomObject]@{
+ EnvelopeId = $envelopeId
+ Status = $status
+ EnvelopeStatus = $envelope.status
+ CompletedDate = $completedRaw
+ Subject = $envelope.emailSubject
+ OutputFormat = $OutputFormat
+ OutputPath = $filePath
+ Error = $errorMessage
+ })
+}
+
+$logRows | Export-Csv -Path $logPath -NoTypeInformation -Encoding UTF8
+
+$downloaded = @($logRows | Where-Object { $_.Status -eq "Downloaded" }).Count
+$failed = @($logRows | Where-Object { $_.Status -eq "Failed" }).Count
+
+Write-Host ""
+Write-Host "DocuSign export complete."
+Write-Host "Completed envelopes found: $($allEnvelopes.Count)"
+Write-Host "Downloaded successfully: $downloaded"
+Write-Host "Failed: $failed"
+Write-Host "Files saved to: $downloadFolder"
+Write-Host "Log saved to: $logPath"
+
+if ($failed -gt 0) {
+ Write-Warning "Some envelopes failed. Review the CSV log and rerun after resolving the errors."
+}
diff --git a/MDM-Enrollment.ps1 b/MDM-Enrollment.ps1
new file mode 100755
index 0000000..4b9557d
--- /dev/null
+++ b/MDM-Enrollment.ps1
@@ -0,0 +1,23 @@
+#!powershell
+
+'''
+
+Created: 2025-01-13
+Modified: 2025-01-15
+Description: This script will enroll the device into MDM.
+
+'''
+
+Write-Host -ForegroundColor Red "WARNING: This script will enroll the current device into MDM. This is meant for Add Source Group owned devices only. Do you want to continue? (Y/N)"
+$answer = Read-Host
+
+if ($answer -ne "Y") {
+ Write-Output "Exiting script - Device will not be enrolled into MDM"
+ exit
+}
+
+Write-Output "Enrolling device into MDM"
+
+Start-Process -FilePath 'C:\Windows\system32\deviceenroller.exe' -ArgumentList '/c /AutoEnrollMDM'
+
+Write-Host -ForegroundColor Green "Device enrolled script has completed. You can now close this window."
\ No newline at end of file
diff --git a/Remove-ClassicTeams.ps1 b/Remove-ClassicTeams.ps1
new file mode 100644
index 0000000..abfd793
--- /dev/null
+++ b/Remove-ClassicTeams.ps1
@@ -0,0 +1,131 @@
+# Remove-ClassicTeams.ps1
+# Removes classic Microsoft Teams and Teams Machine-Wide Installer.
+# Leaves new Teams / MSTeams MSIX app installed.
+
+$ErrorActionPreference = "Continue"
+
+Write-Output "Starting classic Teams cleanup..."
+
+# Stop Teams-related processes
+$processNames = @(
+ "Teams",
+ "ms-teams",
+ "msteams",
+ "Microsoft.Teams",
+ "Update"
+)
+
+foreach ($process in $processNames) {
+ Get-Process -Name $process -ErrorAction SilentlyContinue |
+ Stop-Process -Force -ErrorAction SilentlyContinue
+}
+
+Start-Sleep -Seconds 5
+
+# Remove Teams Machine-Wide Installer from HKLM uninstall registry
+$uninstallPaths = @(
+ "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
+ "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
+)
+
+$machineWideInstallers = foreach ($path in $uninstallPaths) {
+ Get-ItemProperty $path -ErrorAction SilentlyContinue | Where-Object {
+ $_.DisplayName -eq "Teams Machine-Wide Installer"
+ }
+}
+
+foreach ($app in $machineWideInstallers) {
+ Write-Output "Found Teams Machine-Wide Installer: $($app.DisplayName) $($app.DisplayVersion)"
+
+ if ($app.UninstallString -match "\{[A-Fa-f0-9\-]{36}\}") {
+ $productCode = $Matches[0]
+ Write-Output "Removing Teams Machine-Wide Installer using MSI product code: $productCode"
+ Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $productCode /qn /norestart" -Wait
+ }
+ elseif ($app.UninstallString) {
+ Write-Output "Attempting uninstall using uninstall string: $($app.UninstallString)"
+ Start-Process -FilePath "cmd.exe" -ArgumentList "/c $($app.UninstallString) /quiet /norestart" -Wait
+ }
+}
+
+# Remove classic Teams per-user installs
+$userProfiles = Get-ChildItem "C:\Users" -Directory -ErrorAction SilentlyContinue | Where-Object {
+ $_.Name -notin @(
+ "Public",
+ "Default",
+ "Default User",
+ "All Users",
+ "defaultuser0"
+ )
+}
+
+foreach ($profile in $userProfiles) {
+ $teamsFolder = Join-Path $profile.FullName "AppData\Local\Microsoft\Teams"
+ $updateExe = Join-Path $teamsFolder "Update.exe"
+
+ if (Test-Path $updateExe) {
+ Write-Output "Uninstalling classic Teams for profile: $($profile.Name)"
+ Start-Process -FilePath $updateExe -ArgumentList "--uninstall -s" -Wait -ErrorAction SilentlyContinue
+ Start-Sleep -Seconds 3
+ }
+
+ if (Test-Path $teamsFolder) {
+ Write-Output "Removing remaining classic Teams folder for profile: $($profile.Name)"
+ Remove-Item -Path $teamsFolder -Recurse -Force -ErrorAction SilentlyContinue
+ }
+}
+
+# Remove common classic Teams leftovers
+$leftoverPaths = @(
+ "$env:ProgramData\Microsoft\Teams",
+ "$env:ProgramData\Microsoft\TeamsMachineInstaller"
+)
+
+foreach ($path in $leftoverPaths) {
+ if (Test-Path $path) {
+ Write-Output "Removing leftover path: $path"
+ Remove-Item -Path $path -Recurse -Force -ErrorAction SilentlyContinue
+ }
+}
+
+# Confirm new Teams remains installed
+$newTeams = Get-AppxPackage -AllUsers | Where-Object {
+ $_.Name -eq "MSTeams"
+}
+
+if ($newTeams) {
+ Write-Output "New Teams detected and left installed:"
+ $newTeams | Select-Object Name, Version, PackageFullName | Format-List | Out-String | Write-Output
+}
+else {
+ Write-Output "WARNING: New Teams / MSTeams was not detected after cleanup."
+}
+
+# Final verification for classic Teams entries
+$remainingMachineWide = foreach ($path in $uninstallPaths) {
+ Get-ItemProperty $path -ErrorAction SilentlyContinue | Where-Object {
+ $_.DisplayName -eq "Teams Machine-Wide Installer"
+ }
+}
+
+$remainingClassicFolders = foreach ($profile in $userProfiles) {
+ $teamsFolder = Join-Path $profile.FullName "AppData\Local\Microsoft\Teams"
+ if (Test-Path $teamsFolder) {
+ $teamsFolder
+ }
+}
+
+if ($remainingMachineWide -or $remainingClassicFolders) {
+ Write-Output "Classic Teams cleanup completed, but some remnants may remain."
+ if ($remainingMachineWide) {
+ $remainingMachineWide | Select-Object DisplayName, DisplayVersion | Format-Table | Out-String | Write-Output
+ }
+ if ($remainingClassicFolders) {
+ $remainingClassicFolders | ForEach-Object { Write-Output "Remaining folder: $_" }
+ }
+ exit 1
+}
+else {
+ Write-Output "Classic Teams and Teams Machine-Wide Installer cleanup completed successfully."
+ exit 0
+}
\ No newline at end of file
diff --git a/Remove-DotNetCore31.ps1 b/Remove-DotNetCore31.ps1
new file mode 100644
index 0000000..b7a87ef
--- /dev/null
+++ b/Remove-DotNetCore31.ps1
@@ -0,0 +1,71 @@
+# Remove-DotNetCore31.ps1
+# Removes unsupported Microsoft .NET Core / ASP.NET Core / Windows Desktop Runtime / SDK 3.1.x components
+
+$ErrorActionPreference = "Continue"
+
+Write-Output "Starting .NET Core 3.1 remediation..."
+
+$registryPaths = @(
+ "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
+ "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
+)
+
+$targetApps = foreach ($path in $registryPaths) {
+ Get-ItemProperty $path -ErrorAction SilentlyContinue | Where-Object {
+ (
+ $_.DisplayName -match "Microsoft \.NET.*3\.1\." -or
+ $_.DisplayName -match "Microsoft ASP\.NET Core.*3\.1\." -or
+ $_.DisplayName -match "Microsoft Windows Desktop Runtime.*3\.1\."
+ ) -and
+ $_.UninstallString
+ }
+}
+
+if (-not $targetApps) {
+ Write-Output "No .NET Core 3.1 components found. Nothing to remove."
+ exit 0
+}
+
+foreach ($app in $targetApps) {
+ Write-Output "Found: $($app.DisplayName) $($app.DisplayVersion)"
+}
+
+foreach ($app in $targetApps) {
+ Write-Output "Attempting to remove: $($app.DisplayName) $($app.DisplayVersion)"
+
+ $uninstallString = $app.UninstallString
+
+ if ($uninstallString -match "\{[A-Fa-f0-9\-]{36}\}") {
+ $productCode = $Matches[0]
+ Write-Output "Using MSI product code: $productCode"
+ Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $productCode /qn /norestart" -Wait
+ }
+ else {
+ Write-Output "No MSI product code found for $($app.DisplayName). Skipping."
+ }
+}
+
+Write-Output "Verifying remaining .NET Core 3.1 components..."
+
+$remainingApps = foreach ($path in $registryPaths) {
+ Get-ItemProperty $path -ErrorAction SilentlyContinue | Where-Object {
+ (
+ $_.DisplayName -match "Microsoft \.NET.*3\.1\." -or
+ $_.DisplayName -match "Microsoft ASP\.NET Core.*3\.1\." -or
+ $_.DisplayName -match "Microsoft Windows Desktop Runtime.*3\.1\."
+ ) -and
+ $_.UninstallString
+ }
+}
+
+if ($remainingApps) {
+ Write-Output "Some .NET Core 3.1 components may still remain:"
+ $remainingApps | ForEach-Object {
+ Write-Output "$($_.DisplayName) $($_.DisplayVersion)"
+ }
+ exit 1
+}
+else {
+ Write-Output ".NET Core 3.1 components removed successfully."
+ exit 0
+}
\ No newline at end of file
diff --git a/Remove-SpecificTeamsVersions.ps1 b/Remove-SpecificTeamsVersions.ps1
new file mode 100644
index 0000000..d8f13fa
--- /dev/null
+++ b/Remove-SpecificTeamsVersions.ps1
@@ -0,0 +1,233 @@
+# Remove-SpecificTeamsVersions.ps1
+# Removes specified vulnerable/old versions of classic Microsoft Teams and Teams Machine-Wide Installer.
+# Leaves new Teams / MSTeams MSIX app installed.
+
+$ErrorActionPreference = "Continue"
+
+Write-Output "Starting targeted Microsoft Teams cleanup..."
+
+# Versions to remove
+$TargetVersions = @(
+ "24334.1103.3302.5694",
+ "25045.406.3471.2143",
+ "24124.2402.2858.5617",
+ "24004.1403.2634.2418",
+ "23195.1506.2253.6778",
+ "24277.3102.3183.2670",
+ "24256.2502.3123.1",
+ "24165.1306.2986.9504",
+ "25031.702.3408.1909",
+ "24244.507.3118.4732",
+ "23119.303.2080.2726",
+ "1.7.0.7956",
+ "25044.2208.3471.2155",
+ "24348.802.3311.5092",
+ "23335.242.2641.4129"
+)
+
+# Stop Teams-related processes
+$ProcessNames = @(
+ "Teams",
+ "ms-teams",
+ "msteams",
+ "Microsoft.Teams",
+ "Update"
+)
+
+foreach ($ProcessName in $ProcessNames) {
+ Get-Process -Name $ProcessName -ErrorAction SilentlyContinue |
+ Stop-Process -Force -ErrorAction SilentlyContinue
+}
+
+Start-Sleep -Seconds 5
+
+# Registry uninstall locations
+$UninstallRegistryPaths = @(
+ "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
+ "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
+)
+
+# Remove matching Teams Machine-Wide Installer versions
+Write-Output "Checking for targeted Teams Machine-Wide Installer versions..."
+
+$MachineWideInstallers = foreach ($Path in $UninstallRegistryPaths) {
+ Get-ItemProperty $Path -ErrorAction SilentlyContinue | Where-Object {
+ $_.DisplayName -eq "Teams Machine-Wide Installer" -and
+ $TargetVersions -contains $_.DisplayVersion
+ }
+}
+
+foreach ($App in $MachineWideInstallers) {
+ Write-Output "Found targeted Teams Machine-Wide Installer: $($App.DisplayVersion)"
+
+ if ($App.UninstallString -match "\{[A-Fa-f0-9\-]{36}\}") {
+ $ProductCode = $Matches[0]
+ Write-Output "Removing Teams Machine-Wide Installer using MSI product code: $ProductCode"
+
+ Start-Process -FilePath "msiexec.exe" `
+ -ArgumentList "/x $ProductCode /qn /norestart" `
+ -Wait
+ }
+ elseif ($App.UninstallString) {
+ Write-Output "Removing Teams Machine-Wide Installer using uninstall string."
+
+ Start-Process -FilePath "cmd.exe" `
+ -ArgumentList "/c $($App.UninstallString) /quiet /norestart" `
+ -Wait
+ }
+ else {
+ Write-Output "No uninstall string found for Teams Machine-Wide Installer version $($App.DisplayVersion)."
+ }
+}
+
+# Remove matching classic Teams per-user installs
+Write-Output "Checking user profiles for targeted classic Teams versions..."
+
+$UserProfiles = Get-ChildItem "C:\Users" -Directory -ErrorAction SilentlyContinue | Where-Object {
+ $_.Name -notin @(
+ "Public",
+ "Default",
+ "Default User",
+ "All Users",
+ "defaultuser0"
+ )
+}
+
+foreach ($Profile in $UserProfiles) {
+ $TeamsFolder = Join-Path $Profile.FullName "AppData\Local\Microsoft\Teams"
+ $UpdateExe = Join-Path $TeamsFolder "Update.exe"
+ $CurrentExe = Join-Path $TeamsFolder "current\Teams.exe"
+
+ if (-not (Test-Path $TeamsFolder)) {
+ continue
+ }
+
+ $DetectedVersion = $null
+
+ if (Test-Path $CurrentExe) {
+ $DetectedVersion = (Get-Item $CurrentExe).VersionInfo.FileVersion
+ }
+
+ if (-not $DetectedVersion -and (Test-Path $UpdateExe)) {
+ $DetectedVersion = (Get-Item $UpdateExe).VersionInfo.FileVersion
+ }
+
+ if ($TargetVersions -contains $DetectedVersion) {
+ Write-Output "Found targeted classic Teams version $DetectedVersion in profile: $($Profile.Name)"
+
+ if (Test-Path $UpdateExe) {
+ Write-Output "Attempting silent uninstall for profile: $($Profile.Name)"
+ Start-Process -FilePath $UpdateExe `
+ -ArgumentList "--uninstall -s" `
+ -Wait `
+ -ErrorAction SilentlyContinue
+
+ Start-Sleep -Seconds 3
+ }
+
+ if (Test-Path $TeamsFolder) {
+ Write-Output "Removing remaining classic Teams folder for profile: $($Profile.Name)"
+ Remove-Item -Path $TeamsFolder -Recurse -Force -ErrorAction SilentlyContinue
+ }
+ }
+ else {
+ Write-Output "Skipping profile $($Profile.Name). Classic Teams version '$DetectedVersion' is not in target list."
+ }
+}
+
+# Check HKCU-style per-user uninstall entries under user registry hives if loaded
+Write-Output "Checking loaded user registry hives for targeted classic Teams uninstall entries..."
+
+$LoadedUserUninstallPaths = Get-ChildItem "Registry::HKEY_USERS" -ErrorAction SilentlyContinue | ForEach-Object {
+ "Registry::$($_.Name)\Software\Microsoft\Windows\CurrentVersion\Uninstall\*"
+}
+
+foreach ($Path in $LoadedUserUninstallPaths) {
+ $UserTeamsApps = Get-ItemProperty $Path -ErrorAction SilentlyContinue | Where-Object {
+ (
+ $_.DisplayName -eq "Microsoft Teams" -or
+ $_.DisplayName -eq "MicrosoftTeams"
+ ) -and
+ $TargetVersions -contains $_.DisplayVersion
+ }
+
+ foreach ($App in $UserTeamsApps) {
+ Write-Output "Found targeted per-user Teams uninstall entry: $($App.DisplayName) $($App.DisplayVersion)"
+
+ if ($App.UninstallString) {
+ Start-Process -FilePath "cmd.exe" `
+ -ArgumentList "/c $($App.UninstallString) --uninstall -s" `
+ -Wait `
+ -ErrorAction SilentlyContinue
+ }
+ }
+}
+
+# Verify new Teams remains installed
+Write-Output "Checking for new Teams / MSTeams..."
+
+$NewTeams = Get-AppxPackage -AllUsers -ErrorAction SilentlyContinue | Where-Object {
+ $_.Name -eq "MSTeams"
+}
+
+if ($NewTeams) {
+ Write-Output "New Teams / MSTeams is installed and was left in place:"
+ $NewTeams | Select-Object Name, Version, PackageFullName | Format-List | Out-String | Write-Output
+}
+else {
+ Write-Output "WARNING: New Teams / MSTeams was not detected."
+}
+
+# Final verification
+Write-Output "Performing final verification..."
+
+$RemainingMachineWide = foreach ($Path in $UninstallRegistryPaths) {
+ Get-ItemProperty $Path -ErrorAction SilentlyContinue | Where-Object {
+ $_.DisplayName -eq "Teams Machine-Wide Installer" -and
+ $TargetVersions -contains $_.DisplayVersion
+ }
+}
+
+$RemainingClassicTeamsFolders = foreach ($Profile in $UserProfiles) {
+ $TeamsFolder = Join-Path $Profile.FullName "AppData\Local\Microsoft\Teams"
+ $CurrentExe = Join-Path $TeamsFolder "current\Teams.exe"
+
+ if (Test-Path $CurrentExe) {
+ $Version = (Get-Item $CurrentExe).VersionInfo.FileVersion
+
+ if ($TargetVersions -contains $Version) {
+ [PSCustomObject]@{
+ Profile = $Profile.Name
+ Version = $Version
+ Path = $TeamsFolder
+ }
+ }
+ }
+}
+
+if ($RemainingMachineWide -or $RemainingClassicTeamsFolders) {
+ Write-Output "Targeted Teams cleanup completed, but some targeted versions may still remain."
+
+ if ($RemainingMachineWide) {
+ Write-Output "Remaining Teams Machine-Wide Installer entries:"
+ $RemainingMachineWide |
+ Select-Object DisplayName, DisplayVersion, UninstallString |
+ Format-Table -AutoSize |
+ Out-String |
+ Write-Output
+ }
+
+ if ($RemainingClassicTeamsFolders) {
+ Write-Output "Remaining classic Teams folders:"
+ $RemainingClassicTeamsFolders |
+ Format-Table -AutoSize |
+ Out-String |
+ Write-Output
+ }
+
+ exit 1
+}
+else {
+ Write-Output "Targeted Teams versions were successfully removed or were not present."
+ exit 0
+}
\ No newline at end of file
diff --git a/Sharepoint-List-Export.ps1 b/Sharepoint-List-Export.ps1
new file mode 100755
index 0000000..ce406c6
--- /dev/null
+++ b/Sharepoint-List-Export.ps1
@@ -0,0 +1,9 @@
+Install-Module Microsoft.Graph.Authentication -Scope CurrentUser
+
+# ---------------------------
+
+& "Test-SharePointDownload.ps1" `
+ -SiteUrl "https://zefiromethane.sharepoint.com/sites/zefirocorp" `
+ -LibraryName "Documents" `
+ -LocalRoot "F:\Zefiro Methane" `
+ -OutputDirectory "F:\SharePointValidation"
\ No newline at end of file
diff --git a/User-Recertification.ps1 b/User-Recertification.ps1
new file mode 100644
index 0000000..bb8e759
--- /dev/null
+++ b/User-Recertification.ps1
@@ -0,0 +1,74 @@
+# Export-M365UserCertification.ps1
+# Requires Microsoft Graph PowerShell SDK
+#
+# To run: .\User-Recertification.ps1 -OutputPath "C:\Users\Matt\OneDrive - Add Source Group\Documents\1- Projects\M365-User-Certification-Export.csv"
+
+param(
+ [string]$OutputPath = ".\M365-User-Certification-Export.csv"
+)
+
+# Install if needed:
+# Install-Module Microsoft.Graph -Scope CurrentUser
+
+Import-Module Microsoft.Graph.Users
+Import-Module Microsoft.Graph.Identity.DirectoryManagement
+
+$Scopes = @(
+ "User.Read.All",
+ "Directory.Read.All",
+ "AuditLog.Read.All"
+)
+
+Connect-MgGraph -Scopes $Scopes
+
+# Build SKU lookup so license GUIDs become readable names
+$SkuLookup = @{}
+Get-MgSubscribedSku -All | ForEach-Object {
+ $SkuLookup[$_.SkuId.ToString()] = $_.SkuPartNumber
+}
+
+$Properties = @(
+ "id",
+ "displayName",
+ "userPrincipalName",
+ "userType",
+ "accountEnabled",
+ "assignedLicenses",
+ "externalUserState",
+ "signInActivity"
+)
+
+$Users = Get-MgUser -All -Property $Properties
+
+$Export = foreach ($User in $Users) {
+ $AssignedLicenses = if ($User.AssignedLicenses.Count -gt 0) {
+ ($User.AssignedLicenses | ForEach-Object {
+ $SkuId = $_.SkuId.ToString()
+ if ($SkuLookup.ContainsKey($SkuId)) {
+ $SkuLookup[$SkuId]
+ }
+ else {
+ $SkuId
+ }
+ }) -join "; "
+ }
+ else {
+ "None"
+ }
+
+ [PSCustomObject]@{
+ Name = $User.DisplayName
+ UserPrincipalName = $User.UserPrincipalName
+ UserType = $User.UserType
+ AccountEnabled = $User.AccountEnabled
+ AssignedLicenses = $AssignedLicenses
+ GuestInvitationState = $User.ExternalUserState
+ LastInteractiveSignInDate = $User.SignInActivity.LastSignInDateTime
+ }
+}
+
+$Export |
+ Sort-Object Name |
+ Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
+
+Write-Host "Export complete: $OutputPath"
\ No newline at end of file
diff --git a/scripts/AddDNSClient.ps1 b/scripts/AddDNSClient.ps1
new file mode 100755
index 0000000..51ed2a0
--- /dev/null
+++ b/scripts/AddDNSClient.ps1
@@ -0,0 +1,56 @@
+#Requires -RunAsAdministrator
+
+<#
+.SYNOPSIS
+ Configures static DNS server addresses on active network adapters.
+
+.DESCRIPTION
+ This script retrieves all active (connected) network interfaces on the endpoint and assigns
+ primary DNS (Quad9) and secondary backup DNS (Cloudflare) servers.
+
+.NOTES
+ Created: 2025-03-02
+ Modified: 2026-10-04
+
+ DNS Server Mappings:
+ - Quad9 Primary: 9.9.9.9 (Malware blocking + DNSSEC)
+ - Quad9 Secondary: 149.112.112.11 (Malware blocking + DNSSEC)
+ - Cloudflare Prim: 1.1.1.2 (Malware blocking)
+ - Cloudflare Sec: 1.0.0.2 (Malware blocking)
+
+ * Note: If adult content blocking is also desired via Cloudflare,
+ use 1.1.1.3 and 1.0.0.3 instead.
+#>
+
+[CmdletBinding(SupportsShouldProcess = $true)]
+param (
+ # Array of DNS IPv4 addresses in priority order
+ [string[]]$DnsServers = @("9.9.9.9", "149.112.112.11", "1.1.1.2", "1.0.0.2")
+)
+
+# 1. Retrieve active/connected network adapters (filters out disabled, virtual, or disconnected NICs)
+$ActiveAdapters = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' }
+
+if (-not $ActiveAdapters) {
+ Write-Warning "No active network adapters found on this endpoint."
+ return
+}
+
+# 2. Apply DNS settings to each active interface
+foreach ($Adapter in $ActiveAdapters) {
+
+ $InterfaceName = $Adapter.Name
+ $InterfaceIndex = $Adapter.InterfaceIndex
+
+ if ($PSCmdlet.ShouldProcess("Interface: $InterfaceName (Index: $InterfaceIndex)", "Set DNS servers to $($DnsServers -join ', ')")) {
+ try {
+ # Set the static DNS client addresses
+ Set-DnsClientServerAddress -InterfaceIndex $InterfaceIndex -ServerAddresses $DnsServers -ErrorAction Stop
+
+ Write-Host "Successfully updated DNS for '$InterfaceName' (Index: $InterfaceIndex)" -ForegroundColor Green
+ }
+ catch {
+ Write-Error "Failed to update DNS on interface '$InterfaceName' (Index: $InterfaceIndex). Error: $_"
+ }
+ }
+}
\ No newline at end of file
diff --git a/scripts/BitlockerRegFix.ps1 b/scripts/BitlockerRegFix.ps1
new file mode 100755
index 0000000..163d6fb
--- /dev/null
+++ b/scripts/BitlockerRegFix.ps1
@@ -0,0 +1,90 @@
+#Requires -RunAsAdministrator
+
+<#
+.SYNOPSIS
+ Triggers the Windows TPM Health Attestation Certificate Retrieval scheduled task.
+
+.DESCRIPTION
+ Executes the '\Microsoft\Windows\TPM\Tpm-HASCertRetr' scheduled task to resolve
+ BitLocker device registration, Health Attestation, or TPM certificate retrieval
+ issues on endpoints (frequently used in Microsoft Intune environments).
+
+.NOTES
+ Created: 2025-01-15
+ Modified: 2026-10-04
+#>
+
+[CmdletBinding(SupportsShouldProcess = $true)]
+param (
+ [string]$LogDirectory = "C:\IntuneLogs",
+ [string]$LogFileName = "BitlockerRegFix.log",
+ [string]$TaskPath = "\Microsoft\Windows\TPM\",
+ [string]$TaskName = "Tpm-HASCertRetr"
+)
+
+# Build full log file path
+$LogPath = Join-Path -Path $LogDirectory -ChildPath $LogFileName
+
+# Helper function for structured, timestamped logging
+function Write-Log {
+ param (
+ [string]$Message,
+ [ValidateSet("INFO", "WARNING", "ERROR")]
+ [string]$Level = "INFO"
+ )
+ $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
+ $FormattedMessage = "[$Timestamp] [$Level] $Message"
+
+ # Write to console / verbose stream
+ if ($Level -eq "ERROR") {
+ Write-Error $Message
+ } elseif ($Level -eq "WARNING") {
+ Write-Warning $Message
+ } else {
+ Write-Host $FormattedMessage
+ }
+
+ # Append to log file safely
+ try {
+ if (-not (Test-Path -Path $LogDirectory)) {
+ New-Item -Path $LogDirectory -ItemType Directory -Force | Out-Null
+ }
+ Add-Content -Path $LogPath -Value $FormattedMessage -ErrorAction SilentlyContinue
+ }
+ catch {
+ # Fallback if logging fails
+ }
+}
+
+# --- Execution Logic ---
+
+Write-Log "Starting BitLocker/TPM registration fix process..."
+
+# 1. Verify scheduled task exists on the system
+$Task = Get-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName -ErrorAction SilentlyContinue
+
+if (-not $Task) {
+ Write-Log "Scheduled task '$TaskPath$TaskName' was not found on this endpoint." -Level "ERROR"
+ exit 1
+}
+
+# 2. Trigger the scheduled task
+if ($PSCmdlet.ShouldProcess("Scheduled Task: $TaskPath$TaskName", "Execute Task")) {
+ try {
+ Write-Log "Executing scheduled task '$TaskPath$TaskName'..."
+
+ # Trigger task run
+ Start-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName -ErrorAction Stop
+
+ # Brief pause to verify task state
+ Start-Sleep -Seconds 2
+ $UpdatedTask = Get-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName
+
+ Write-Log "Successfully triggered task. Current Task State: $($UpdatedTask.State)" -Level "INFO"
+ exit 0
+ }
+ catch {
+ Write-Log "Failed to execute scheduled task '$TaskPath$TaskName'. Error: $_" -Level "ERROR"
+ exit 1
+ }
+}
\ No newline at end of file
diff --git a/asg_website_backup.sh b/scripts/asg_website_backup.sh
similarity index 100%
rename from asg_website_backup.sh
rename to scripts/asg_website_backup.sh
diff --git a/website/default.html b/website/default.html
new file mode 100755
index 0000000..26d65cd
--- /dev/null
+++ b/website/default.html
@@ -0,0 +1,378 @@
+
+
+
+
+
+Add Source Group Site
+
+
+
+
+
+
+
+
+
+
+
+
Accounting · Finance · HR
+
Experienced accounting professionals, when you need them.
+
Add Source Group is a professional services and placement firm. We provide high-caliber resources on an interim or permanent basis and partner with clients across the country to build strong, efficient teams.
+
+
+
+
+
4–30+ years Experience across our professionals
+
Interim or permanent Flexible engagement models
+
30–50% Typical savings over peer firms
+
+
+
+
What we do
Strengthen your Accounting, Finance and HR teams.
+
+
+
+
+
Why ASG
A partner built for long-term value.
+
+
Flexible timing Scale up or down as your needs change, with smooth transitions to permanent hires.
+
Qualified professionals Practitioners with public and corporate accounting experience.
+
Consistent teams The same people return over time, so they already know your business.
+
Reasonable rates A low-overhead model means deep experience at a fair price.
+
+
+
+Build your highly skilled team. Whether you need an executive-level leader or an experienced staff member, we can help.
Get in touch
+
+
+
+
Services
How we can help Our professionals integrate with your team and serve as technical resources for public and private companies.
+
+
+
Interim and long-term leaders for significant growth, new-company start-ups, or major organizational change — ready to quickly provide oversight of critical business functions.
+
Supplemental Team Members
+
Temporary or long-term professionals for when your team needs more capacity:
+
Open positions and leave coverage Monthly, quarterly and annual deadlines Acquisitions and new system implementations Outsourced accounting, start-ups and wind-downs
+
+
We build relationships with qualified professionals and businesses, because understanding the goals of both leads to successful placements. Our dedicated recruiters source through a deep, active network.
+
+
+
+
+
+
+
Our purpose
+
We work each day to help companies be more successful by providing the highest quality resources and services that add value to their business through meaningful partnerships.
+
+
Core beliefs
What guides us.
+
+
Faith We believe in honoring God in all that we do.
+
Integrity We believe in doing the right thing, the right way, and at the right time.
+
Relationships We believe that continually building new and existing relationships is essential to who we are and what we do.
+
Service We believe in helping others, making people and our clients better.
+
Initiative We believe in taking the initiative and persevering through to the end — proactively looking for opportunities to help our clients and add value.
+
Excellence We believe in striving for excellence in all that we do.
+
+
+
+
+
+
About us
Practitioners who have sat in your seat. Add Source Group specializes in Accounting, Finance and HR. Our team brings Big 4 and corporate leadership experience to clients across many industries, from day-to-day needs to complex projects.
+
+
Russ Lynch Managing Partner
25+ years of corporate finance and accounting leadership. CPA.
+
Wade Watkins Managing Partner
20+ years of public accounting and client service leadership. CPA.
+
Ely Jones Managing Director, Business Development & Recruiting
CPA (Inactive).
+
Jenny Sneed Director, Business Development & Recruiting
CPA (Inactive).
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/website/favicon.ico b/website/favicon.ico
new file mode 100755
index 0000000..af5b84b
Binary files /dev/null and b/website/favicon.ico differ
diff --git a/website/robots.txt b/website/robots.txt
new file mode 100755
index 0000000..de3ad21
--- /dev/null
+++ b/website/robots.txt
@@ -0,0 +1,4 @@
+User-agent: *
+Allow: /
+
+Sitemap: https://addsourcegroup.com/sitemap.xml
\ No newline at end of file
diff --git a/website/sitemap.xml b/website/sitemap.xml
new file mode 100755
index 0000000..22992ac
--- /dev/null
+++ b/website/sitemap.xml
@@ -0,0 +1,6 @@
+
+
+
+ https://addsourcegroup.com/
+
+
\ No newline at end of file