From 4fd95b76d6c93c5e887860bb87db6dbf179027d1 Mon Sep 17 00:00:00 2001 From: Matt Speer Date: Sun, 4 Oct 2026 10:22:22 -0500 Subject: [PATCH] Initial commit for powershell scripts. --- .gitignore | 1 + DNS Detection Script.ps1 | 28 ++ DirectoryListing.ps1 | 209 ++++++++++ Download-All-SharePoint-Files.ps1 | 112 ++++++ Export-DocuSignCompletedAgreements.ps1 | 378 ++++++++++++++++++ MDM-Enrollment.ps1 | 23 ++ Remove-ClassicTeams.ps1 | 131 ++++++ Remove-DotNetCore31.ps1 | 71 ++++ Remove-SpecificTeamsVersions.ps1 | 233 +++++++++++ Sharepoint-List-Export.ps1 | 9 + User-Recertification.ps1 | 74 ++++ scripts/AddDNSClient.ps1 | 56 +++ scripts/BitlockerRegFix.ps1 | 90 +++++ .../asg_website_backup.sh | 0 website/default.html | 378 ++++++++++++++++++ website/favicon.ico | Bin 0 -> 12781 bytes website/robots.txt | 4 + website/sitemap.xml | 6 + 18 files changed, 1803 insertions(+) create mode 100644 .gitignore create mode 100755 DNS Detection Script.ps1 create mode 100755 DirectoryListing.ps1 create mode 100755 Download-All-SharePoint-Files.ps1 create mode 100644 Export-DocuSignCompletedAgreements.ps1 create mode 100755 MDM-Enrollment.ps1 create mode 100644 Remove-ClassicTeams.ps1 create mode 100644 Remove-DotNetCore31.ps1 create mode 100644 Remove-SpecificTeamsVersions.ps1 create mode 100755 Sharepoint-List-Export.ps1 create mode 100644 User-Recertification.ps1 create mode 100755 scripts/AddDNSClient.ps1 create mode 100755 scripts/BitlockerRegFix.ps1 rename asg_website_backup.sh => scripts/asg_website_backup.sh (100%) create mode 100755 website/default.html create mode 100755 website/favicon.ico create mode 100755 website/robots.txt create mode 100755 website/sitemap.xml 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( + "" + ) + } + 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 +
    +
    + + + +
    + + + + +"@) + +$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+ yearsExperience across our professionals
    +
    Interim or permanentFlexible engagement models
    +
    30–50%Typical savings over peer firms
    +
    + +
    + +
    +

    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.

    +
    +

    Management Resources

    +

    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
    +

    Recruiting & Placement

    +

    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.

    +
    +
    Portrait of Russ Lynch

    Russ Lynch

    Managing Partner

    25+ years of corporate finance and accounting leadership. CPA.

    +
    Portrait of Wade Watkins

    Wade Watkins

    Managing Partner

    20+ years of public accounting and client service leadership. CPA.

    +
    Portrait of Ely Jones

    Ely Jones

    Managing Director, Business Development & Recruiting

    CPA (Inactive).

    +
    Portrait of Jenny Sneed

    Jenny Sneed

    Director, Business Development & Recruiting

    CPA (Inactive).

    +
    +
    + + +
    +

    Contact

    Let's build your team.

    Tell us what you need. Whether you're looking for interim support, project help, or your next opportunity, we'd love to hear from you.

    +
    +
    + +

    Need talent or project help?

    +

    contact@addsourcegroup.com

    + Email Us +
    +
    + +

    Looking for an opportunity?

    +

    candidates@addsourcegroup.com

    + Contact Our Recruiting Team +
    +
    +
    + +
    + + + + + + diff --git a/website/favicon.ico b/website/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..af5b84b87c5247122e9ca5c71a69d69145456c40 GIT binary patch literal 12781 zcmch718^qM+U6I3u_v}|+qOBejfpX_Z5tC~V%xTzOl(ZFx%d9NwW?eDZ*6T?oj&!P zx6bLeyI(x#?FIlq01yBqB*0fs2w(sO0Mr2h03qSOa6UKy5d1a9$oMbp0|fw(e>FkD zf8p;i06-xE0O0TcFMNgx0Dxiv0O5-A67VqCUs3?Pl%%NgSO0Yd01^=L>r8O>Y53KF zI4esC1FENR|9k;(b0Ik)0H7`w_RSFD3qw0dYB>V{2>t(PAj9@$CSMZS# zG;gjoYzWj8h{7Qg$_h`n5_%lgXR_fk(DQg!krr(bATZ_Gb(Qx+jUD!+D(Y!YmbfUn0H3AwAg5zHEBZ(fJ4=`IPfYz&FpwJlW5n@7Ps19 zEm#PRS_~YV)5Q>-e5_pDeUsCVTioWW%~J2YSUIO#1)9lPh)IOFN&hg5;8BTvcDl^O z-PYFBXm$38Z}BZ+vQB+HejJN7SD)TRi?xpdxpJ3Y!TZ6is$VY|ASEU*S}kl4@V_w^ z!Y?u5Kh4G2)ARld{HM(Yoc$l>qA78aY^|tjiaV5wr1@idb^K?ibBdn93LUV6{+p(_ zJ`yyz2?zXeh*d~ARuxtWE0bg;dVo4OQc4!tFOyA)|a<{8^O|#jEeU zb{D!dOM*#-{iZR;ZxR8O*l&BPCfOfoG+hpPf1PlavVRcYRiYjR{5BZ;yd8o_@{#Qs#lrUGZ8@buBa>bf`=bhAf{!54^m zp;92`RE>~GP*Hrpp*ZQd<@0?jhmteTxH%6Y1_n!CM;?Y)&~xH`uz*f7{Tq4^W8A>CGvw_^j*-6(Ks){l z8aF?jaXhPn5DmZI@YhpY12oy%2Z7@QbC{_VmFZ^mzIBHY!IbAB?fHy+GKMhhry!E3 zu}rEtk!}G9$)b>LL?{s(GP81lW~qI^yCswvFS=fH#X0C@=ARtZhVGjm=+%9 zQH^Oc|Nyt!$L(`y``TM0=mkvMR;65m)=p!@cM`KfSe&HEP6`P00 z=#p}ac6Ujf6s+>@M==HkwnDGErf7q*-zjMVM#g?#+2UP3J>k$6fhP5IQ4}l z(zbX`Mps|bNT$7FidLdbAFNvJFS2C`_Wq4Nd0CDA*ac}rh(Xv(){r?tJFnF+d~OR( zIf8QIU~^kqoQ)@Y>T~&8C}iJIS~utc=ey{s3Kmj5HH?_Jz1pL;mAqM|zb#FT;9+%a zt!FemaXDmERFXvSee!Cf-!J}+Xn3)~oew%1s@{JrB9AB1p3REfYGa|uNysFin8J>=KQ+=^_39;jQ&olusdM_3Z`$h$OQ=M(kW0jZcV@$YFwY(nc!C27`ZwgK zf0yAGWCW2ZPMi{T1{~6jB+1v-yZ$Wx?c4x%sOJW{G{t>>4{1%IsktU%Q|-T^>-*XK zp^Z=;ycwuD_S#A!I6A(_&_idzxe+Q_u$ftic_~7?gO38;9-~K;T+aE}N@76d<}bS& zrh!3DNcc_)S%e!*&V`q8Q0RT(#|h@swF-~JV9y_Z9OJKQhFClvdD890!y5W1>}-JX z_ww@_-;-_V_Rs7Y(u)8;D->O}pGc9Fx!L}~eM;u%-GX>OF=qURF)$r0SlTZ2qo2=7 zBK4E$Dr$o*Pse<;lT!U*jnOkjl_8Pe zzlVu6DNqn5l<%CrpoB<>-W)|A|1pW0M0fC;h05<2CoV1-jh2{+%XfGd zh=D`Vu@j|qDo6aF1zHyXnvF!3mEj96rV**+T8O>KVXdg{fsUn>o_1|Viy=u`7Z{xh z1^Vb0)LYKNxMP6(h?XmOVeiIt>%a?|SW5IfLfd%fhi7zGnzphb450(%hWo}8%#q0_ zkQ3Xzg-BWvv$g5)D2yOoo$Nr2(`?gwGm)$4S{Q-M$O$J*1HET9abzMxGv-jt7`wcY zniS-m%5c5%u&zxjtqI8zPDiUY1zf?3zOF3C%=(}&XgDIKH|G+L@$rvCtpoBP!ya>V z5Phr2k@dglBKIVYs7WTQ;Uv)FGM~wpajWK@M8%S>t*r!-9Tul?*@J-hh8;FKuCseT z-?*%-wHMTo17FA>C@JYooIV1DiJEO;VS}-29&Sa}>RgKO1R}!fnBu4wkfc-{?jD4? z7xS;*nQ{4018@G05#H;d!fdd@s&fx4Xkh{5Mf`bz>M4HNJDhgZXDs!xp3 ziY`?44-hq;P5;jG0bDiH zOaWA?G58&&gqRJYt&WoCMDa~ORgZdxszo+`YuX^fdSt_7kP5EFVhg0ZW;>BalL?|v zOmRiZZ}D+@B%rv)SvDN+S{*Jzp%76`>NJ8|EkEKm8(-n$3;bXX!~xCDQ$7r6-olSQ zk-9QGNVAB-#_8uj*~fUU)uKGj5H)65w(PH_YR%1it_Nc=PRTz(m-vUn{n(!20`p$ zxV6K_m{?5i{YR*7+)y|>1Rm&u$9XnyyfMXuv)k4)3@t~>RNQ;B3o}FTb%+39qbt>>Gpz3e3?-}dJS!y5_YQJUuUK&V8KbKtaEa-YDs@C zlcF%VoehdvW?87r5D6|4;IM>OC`eVlSn<6<1sf=iCr@;bu-k5iwEStPy?P}jR{$Sr95QqA?(|?8vmSfF6I9mjaKYjLCl=6Ub5H z=LR2%GpV0Ar}ZV$8)*-c=iej(RuQ;F-Kg*jPF7(rd}v1&(`X3U?MDs#K5OY)w*KAX zaZLVNQd9t!OaHw$Gc6*?!Qw1f?R&LxLGqfkey))v9X5&Zg7&CibfPy3K9OLE&GF%6 zNYR**#Ln{Td&CTHIuEA}_*bN;S3N=f$&QDKI|Z#$9Ip9wPrfNfWpoJ=CmFoVK9}B= zo3+Si?6!Ry&txZyeuS^3{7sGt3VkHtCx+7XAAwQ#FBXiG&9mtgC@yk@?{m$_4}RG8 z&)z<`=}CJ)7loU-SD*JQ;bE{(aJh%m;a#o$oDjccfBtI$B7>@sWI2|1ox!7zZR$y^ z|1JmpM@ky{&O2l*J(Se*we9@3$ye}8Ecnml`^(aC>kIs+cL&h-|46>>`WM zXs$!2OW6zg#Y-DokEWdF=FB(YfoE%%3eR7D?~hMi+fV#g5AVvVDNR)d6N*$2Ly)K- z(m(5ZMg5fxY#5RA&@gdi!72aeVFSxEI2xW*D*;Tf%5&{AU~Q8<2`MH{<%OOrSr3Es zk0_AOX@A5(Ky)$5f?1P&$Qu5)$*f-|-i+WDtT7HJ2hgd#g*$r^QI(*}MEMS<7f`S` z$JC_WNZ@63xU-&$gpI@UB!oB2pnD8Ng#5p$QCygxxOTZgZL-9knic$`SyrtpOf`V1Q%7lmy)feuF$D)vpO=sKd8Gf<9Ic)+Pl4rPp9{ zS$G&3?$DkYUOlMZP`l8;DXt_G_w-wy5j+Jz%T#etTAJcp@l@eym1-PW7A!5uEUb1l zZ7(Vmb(%#3OV}a#0;b_J*2caFro|C0`17SsOkNTmrkf+;l*YmCWo)uL$j(c*pZL-| zicPm02#9$zA*Usct~ZpRZ8c9n53->7U+YjKEm4!`biqP7x{Si`A!Hv&gx&*77A7|( zPO~X3|C_{;g3T*l{WG}kWqTtqoMHBBLk$|}khOPM zv_Vh)L>3li8n$?bW~;c5bn8USo+OtCn_`3W(qPgA$-B3UG-=H5ENMEwkxE^I zxW=4Uil{iV)BHR7pz6Ecq=NmYTOpoqF2YeUQCO9MCV+eJXCF4${M2QmgVse%4_`L* zL);XUmUm;OKIH-=PkvAm`s$&u12u=m$DgepcWxy9Q;*k=LQ7S+s{9oLB~|NWePgsc zZrt_0^Jc32UO}&7FYbz2u?Z2O0dEPyXer;u?+S`(O!zKXogD`MrqK~I&@&)s7u=!* z>MLp0t$1o`MR>hdG(-=K_e&P^xzjx zI3{5=g%)O@7snB(`BG)p-o!38tc*cXSt$Z)5TeE7iM;~~F2%*$m2TO?9eyC1hhtC+ z|F7^G*@Sj2rc6pr!pVI8@H7Dl8wnm>+kH}FsjYa1(o(#-|9+`oPa8Z_f)vG67P8k{ z=eZ--*V8nz7_rc!veYh^zD35&Z9p<+IWn^Os~gqIi$nB9o``M9hf1cAgG}jqY7_l} zM22H#G*81S&_P^&dDVclc#_B<{CEg79HHr8u$1jfq5Te>{0l_Y zb>aH7Vlcf()Mlj(Ua##b9`g8%fS4W2m0Q6{Ae~#}d`>8!Ns91kvc(tuE|pefT^^sY z*$O&38TEXvQEVGs2>5s09ULD&wnvj#772|9zgL2PXlK)SM*NH@=+E@4I_Qc`+DFB5 zRqEt-aY_O%H@kc{fo|$f|LM-ZR)f&hM25!<#e@#enB^YHW3m~znWW40kA>_5d5!h5 z%I(*NOdq^%AgT|vCVWpCIy_QH&%9RW;4Us_Wb;9MQjfo_$w)0|f3d5P(N<>v;#BBJ z2H0))W#HhnD9nUm z6Z-Q#8vNw=IfCtMKx^r8s`GV+fEA|M`3iwD>59N_v`;9=Q(mT?{$wEYw>D>7A?`EaTsv=g(g>(Xq(l zE0Q9I92P&_#~@YN-=di(dzD8JkIke+6;{(AAuIh= z+Iu}~*y4K^Qc2^b8^U&0NH%ZfspW~IF9V4Fak586RoXq_@W{+usWWxrh+1lJR=s)4 zLo!UOSx2t?G3H6lFMpb-M9%e!h}RdTbw^54Z?_qjVFu39P?O)#o@yewFoZdqPj`Bq zC>PT_7`#hUYrX9Ywo02Db(Wrxe>D#XxB4=|k3CZwva!D-46$KJz+b02BXH{F8F_M0 zZsl5L0kT0lTqh?-;qrDll2QsHtBFqiEk*DNqFycmO~>?qbw}(990enn@amp=K$7^% zNVGx?=cjTDs51L#CTAH+%c84?|X`MB;!;Jlh|bdY4rT`&yi2 zknqs)D2M2Je`eYXut7R>uM7HKJKD_!T0vQUwQ*Op5iN6b{tj$6_xl;T48Y1kMu3l#Ih;G;782@j#HRYegaI zDALnLIn)0tEWLedZ+CaSx5PeOdQqlOrhDm~={#+Ycbez6KQ%wCn&&$IuF&qs;ckTG zd4RxM3y=pyi%|Fv<)aGj&p_uno`BDAx`0~Xa)YoUWCO56|DTFi)Ld`Ge`Eiy`pDbw z#xZinuwPMgz6nv8)(H&}%+zo#x5Wi@7n(%E``lz))1~k?`yVw^RpeB^^Y8SL zThVazAm-$3>8taktN5`v1FhJ*qq(UPb$q@Fj&IxETjAV;nuoZteJiqL$-RiOV6_ZSM?Ni9s;0BkO$73RgDM@2FMx<0hK*YcbA9P-xn2_r!yyrM@PN6_=!KsDamPTn%xvVY7#Vl5nGG503Q`D)`8QzT1_3sa4J1VsPYuDb5rFR1 zZ|YNC6c-XTDEIQxjUKVLJe(K6x23_phQMxle;8Plo>};KNkIo~;(&yw6oc4R zdy)XU+UX&_;$xyeP&?grSiC;(UynpH4LE}?6p{^;R}fUZr3WuQujhPZKOZPN7g_Py zXNLLwOH%!DPlB*TKd_QT0+ihf7kpr$YDY?_gZem2>6ve4IltJfG&ut=1I7{c;B9^A zA!T*I6JhZnXVv5NmaoeV=Ota7B)@SD<#L9p__aYDZYSsZ_ADeyAg9&K8GOJYXSBlV zElKpxHz)o>cT|u!Y;t$aAx_a@Qt0Hh@U>>P(fhuy+gx2 z!cRa75)d zK&f^AodtKe>8+S>59qYGRR7p1JfSVbV(^ydRC{EP4@&av4U(s*iLIZC9(IAe1S!9v z_4bK(|D3%V0e2KPQPqBVj@)>}^=yrzKFY`HgL|mQJyn=_3p{L(!JFH!Bm(rfiCA6X z`M|;u<>RKr= zz*sm48BKpT&OW*0whc`Nr`kiUtP31-1K{;H7ooD-AB3j;uS-=D;A=ZM&aL+jm$MC? zGJm=M`EBL2Il2hN(0K&ju7P*BW}R%!i>jVWPVC>*R}th8{8$OWF_@s@Dq;6W zY?tWFcO&4nXL5$cTmjgU-A33XPnY0)3SLe)ktA)C-eCE+jONp@oogXXUL!d1C=bMW z^Xb8;);lJn33oU=wr}rH(V`Nc`VK!TXohH-l_pF&)mI^EI+XB!pRxvje*y#{;)W2B z_)(;B+Cmazh#QYVH87bRXtsW1H=y)xzYnM|7wPz`W8PtS_4oRO0{mL=g1!qVqZ87l zMT4Bug`00Vj+E7e)^2ySr||iK^YNJLx62^#3HJGSJ68_!^Xc;)%J1bR_xa54BN$5H z6EE~KXE1KR0yznr#5Tx$xIpI(&fV4K(;K=6qx#?^_~VW{^9fh=a%;iyp7inosIW>zKxel{7awD=(jFqclcLC0u(SN6eeOd zCl|jKA?d%LI#3!n*Jk!F#Edq8BQddk2W1p`{mv+z?TucJCv*?D!x3kFh88F2R|JBg zD6YF%7D70Fcpb@H8V$NYZ*ou%0P>oZL9jj26$BCb_&s5iO2S-<%Y(2d;(` zkgup^UQBI8h3eTEn0feP3sw_%ehRD7+j!=9Ko64h^YS~P$4(=V^~1QU7Yl< zsc1%w56ck=r!4~cam`RV0jwN&%j3M>1Sei-?Y#$ZJk9(DF81BI;QBG-=K6F6`|)uJ z`2>RqptV)2g)>kcDPkDDhgOr%v1q*@UGMtfj0h%T(Qjs3=kfYOi4na*z0Jr3W$GK3 z`&4&sx2`|pu@7;4&jv~IZn0iz^Udm#ZHunUq8ZQ81(hgmo2j?Jnb*qlnbY=72P!6? z9kEr*asw`cB)|)C;n6 z38C%@F7;`+k?c@>oT1)V4;25cZTlIABE0E@nF`9lkkL_raJL~j6sgDGM(@k_pIlOpaPG*IEN3xvUVqszwx1CF1*!U);!l56>yr-rQAb)*GSkvD*&K9++gk zoZ?oa={zaL`>V&t;0xwXoX++0ld=7(BYbO{B+wXk8f#F0n=$i!QMS znahf&8KCsBln@eI|C;n#9ol(Ix8Pv|Eb@8poS0OdnM(r-Fk5FsBZscJ(f|5N?jc(YD@TjVx3ILKq!p}dVz%p^YO zz%+QX5uT_MB~j^KHzkE2Lgj;Dsp z@62UFV2xwqvf^V#ucpzA+PpZLJne^1^&On50zEs|^44#=PV6xQdt3Lv6nEdRfr96a$0H9t{Gq<14Lu-JG96)+jw+?Qj=aySi%v!SEE^^c~q>kK*2AZ zS0Su;UFVHr77tMk{LT~$+wKrMI;-i}HBqi~TssUInq1%A9K9kb+C~Z|?HIbIiP*Kv zvdVFH+hfh@Ry8<$kLW16R|O9em%J_Et+*^DPJA~NH7IIvIAmt7Z(y`N!gV>HgQdrR z2u?E<;}5ZHy4b2jK6SAS_vF(N0MX9>ar~`pwe~0GR$azL7s_-HAmXXrizA`#^Q@ra zTX=?2c(J~2jT-d*ibQ_T>-2!TG-hM=EL|&|sCCny%;}oUDE9=s%ESm20Z~XU zC=c&eGZquj)EZ6_ha5R@YSbQ>{ap>NuSf3W;5)F1v%R~4WD`tL)DZ{q5IAPX#miBo zBYfGhKSd-8+f4RMB|xa7O;-?)C8UHxrZaHD4R#MtAB0Kq`J-0v_{C%W8L^gIzX>uY zu`R@*vCjM~l&I?KX5Kj8%B7qQj?`{iql6{J2rZ(Z5)xqGWdWUe<$C;1Py66Y=8Z4M zZg8B#XXNgDs82;Nx=})BvChFP;%+gAM+ zec-j>Inpqsf+`5@_T@Gy?w@jr`Nbzr<$_jw{o5WG$Mn9Y$Xzd)ECFFeg`$uB1%Tvf zIKQ`~Kb$@vm10Rs{hXV7Ydst&K9XI~D&~?$Q{I8LR5=ra8Fi>7k{5#6jo9Y2rHo(Y zH_rCOhEsxdgCxtDN_g%c7tt|#Vfu%l%VgTWuRvqhmPU8(22@b}rIk8z$Jhy8?Kj$k z2S_Alx||Wy?OAv;F*Krxnvl*oY*P--mG)`+%i1-JC(;SXAc(NFY+2bT17XGPFFTh*C7Up!mIL}t}%~p|RLf$2p0}^yN z)EEQ;SDp1TEb>?)#+$M`H7I3%`l_R}S(_M2anN&_n9P6|=S4ajhca4K4grml;N!3W zDU9UMr7CO=zZ}zHAfh8J8@d=rq~q&)J3A^T8?t0T2g?hlzysLAulcWEsGxhw`q24x zz$K^oV|r$QBp|B&P|#Yt0@Ni;VOI8d!c5y4sw zxAm>e96CjuPW+>l1?5qX_jI>d1CIb3gxT47hQp|=&cr4Vzz?r&Fgu5STY0M=+qbw! z9IhX7&l+@(^L9q+uPku2#RD#7Y56TbTY}p7FvFFZ!1v|B>Wj1ga(J-ue}OG9wX>mt zfx~1TV|7&-X9-1t25zlaX)u1H(+Lm!jz2i+Y;K}yNN*GgOd8e#Uw%kO?YMVB@8Ge_ zAoV)k4FV_B8j{N*hvhu=3>Nk$8#fliPgFsfz&-M0f(joyXi=~js7VA2m207j4%15X zE~ktY26VT1gUp-zf#*UFp-_d- zf~S}F#1OQ<%Fu<;My0iK7En0Fl*fyBJqc+-<+e}5Qf%Lgj%twnhZK+2NXetevIUch zFEg27(2VDsE-V&0vP}+U)h9LRp~(WySF#C7)lvOD%l;@47$S?*GMZt1qt_x=Q3y~I zB=#LYu6HoOj}E$St~P!twceSZX`w9f-98ScAO@W66q)cTDG@y zo1*n!I~LOQNIg^4 z^`t@2;o+1i!YQWPQPcYU1dkWVhx+0m#c4&5Bs{2zI!^+{ntIOYa>e`s^BSF03NhX-{+&$r@o=>cohJ=9nh3 zaKfAL+QaLpBB!0v6Ix+Ii_2Ne#-l`;nhsx?#W)l z-rAtX-WE$zH>khGo*~a*{HR&U?3!Q--K;=xIslEHi&>N8xY`sovulbAzF4SKBTx}Y zqqBy#LZNhO1&TU63OUuoO%|#6)Sz(1 z99HrWT51Q^y6PIOJNaZcOF$pC(P{;3&U$7^ZnHs5J4VWLxyIxG*!kAegt$zktE!AO z#pdQxX4aK6$$U*zuHfVb8jiWU39J> zA$E4?4d?;MV#JpeaaFs)-vydzWzyXaT729h#nN#?vF`qo<9y$blnUVYq4tey@kR3ss? zx1`#?9sF7Gdkzco3em?_Co1MDa}RMsfX7Yb8a!y(E;_&&tVdxizMN?+W;hb1j~j>` zP~rzOR#of*Qw)61qum;R*f>{=T%_ElD|8 z-RWiOr6FkHr`4drkhvC6@J7!UP-mr=)o8s%ZhFoY7nYWs+qb;^tc9LS$O9oe%(Sz&00`6Z4SCKI;2+TJ37%*@gYq7CuS@KlB^Y4#x0L3ammU zhAth}d~|&jvlKf*Y^6Is6K>-4-ZK%^_`+#`nwU^4P*9O*h&Zh7zzQ~2d!P=2ztC4q`wXM~I@ISCf)puwYP0pj9xCJL1Q930rk64jxmSeRmk+G} z#_#}Ct9hmA3{hB@{6|u&>$3c)PD{&xW%-;0iAU9XHP9UnG>N`zWhn6-3Mtk~ literal 0 HcmV?d00001 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