-
Notifications
You must be signed in to change notification settings - Fork 50
Open
Description
# Import necessary modules for GUI and Active Directory
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Check and import required modules with error handling
try {
Import-Module ActiveDirectory -ErrorAction Stop
Import-Module GroupPolicy -ErrorAction Stop
} catch {
Write-Error "Required modules (ActiveDirectory, GroupPolicy) are not available. Please install RSAT tools."
return
}
# Define the main function
function Invoke-ScriptSentry {
<#
.SYNOPSIS
ScriptSentry finds misconfigured and dangerous logon scripts.
.DESCRIPTION
ScriptSentry searches the NETLOGON share & Group Policy to identify various security issues in logon scripts, including plaintext credentials, dangerous permissions, elevated privileges, malicious code, unsigned scripts, sensitive information, insecure practices, known vulnerable scripts, non-standard locations, unsafe GPO settings, obfuscated code, invalid paths, external resource references, and integration with threat intelligence. It also provides remediation suggestions for identified issues.
.PARAMETER SaveOutput
Save the output to a file
.PARAMETER VTApiKey
VirusTotal API key for threat intelligence checks
.EXAMPLE
Invoke-ScriptSentry
.EXAMPLE
Invoke-ScriptSentry | Out-File c:\temp\ScriptSentry.txt
.EXAMPLE
Invoke-ScriptSentry -SaveOutput $true -VTApiKey "your_api_key"
#>
[CmdletBinding()]
Param(
[boolean]$SaveOutput = $false,
[string]$VTApiKey = ""
)
# Helper function to get logon scripts
function Get-LogonScripts {
$Scripts = @()
$AccessIssues = @()
try {
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
} catch {
Write-Error "Unable to get current domain: $($_.Exception.Message)"
return $Scripts, $AccessIssues
}
$NetlogonPath = "\\$Domain\NETLOGON"
$SysvolPath = "\\$Domain\SYSVOL\$Domain\Policies"
# Try to access NETLOGON
try {
if (Test-Path $NetlogonPath -ErrorAction Stop) {
$NetlogonScripts = Get-ChildItem -Path $NetlogonPath -Recurse -File -Include *.bat, *.cmd, *.ps1, *.vbs -ErrorAction Stop
$Scripts += $NetlogonScripts
Write-Verbose "Found $($NetlogonScripts.Count) scripts in NETLOGON"
}
} catch {
$AccessIssues += [pscustomobject]@{
Type = 'AccessDenied'
Path = $NetlogonPath
Error = $_.Exception.Message
}
}
# Try to access SYSVOL
try {
if (Test-Path $SysvolPath -ErrorAction Stop) {
$SysvolScripts = Get-ChildItem -Path $SysvolPath -Recurse -File -Include *.bat, *.cmd, *.ps1, *.vbs -ErrorAction Stop
$Scripts += $SysvolScripts
Write-Verbose "Found $($SysvolScripts.Count) scripts in SYSVOL"
}
} catch {
$AccessIssues += [pscustomobject]@{
Type = 'AccessDenied'
Path = $SysvolPath
Error = $_.Exception.Message
}
}
return $Scripts, $AccessIssues
}
# Check for elevated scripts
function Find-ElevatedScripts {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[array]$LogonScripts
)
$ElevationPatterns = @(
'runas',
'Start-Process.*-Verb.*RunAs',
'sudo',
'psexec.*-s',
'elevate',
'UAC'
)
$results = @()
foreach ($script in $LogonScripts) {
try {
$content = Get-Content -Path $script.FullName -Raw -ErrorAction Stop
foreach ($pattern in $ElevationPatterns) {
if ($content -match $pattern) {
$results += [pscustomobject]@{
Type = 'ElevatedScript'
File = $script.FullName
Pattern = $pattern
}
}
}
} catch {
Write-Warning "Could not read script: $($script.FullName) - $($_.Exception.Message)"
}
}
return $results
}
# Check for malicious code
function Find-MaliciousCode {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[array]$LogonScripts
)
$MaliciousPatterns = @(
'Invoke-WebRequest',
'IEX\s*\(',
'DownloadString',
'Invoke-Expression',
'System\.Net\.WebClient',
'wget',
'curl.*-o',
'bitsadmin.*transfer'
)
$results = @()
foreach ($script in $LogonScripts) {
try {
$content = Get-Content -Path $script.FullName -Raw -ErrorAction Stop
foreach ($pattern in $MaliciousPatterns) {
if ($content -match $pattern) {
$results += [pscustomobject]@{
Type = 'MaliciousCode'
File = $script.FullName
Pattern = $pattern
}
}
}
} catch {
Write-Warning "Could not read script: $($script.FullName) - $($_.Exception.Message)"
}
}
return $results
}
# Check for unsigned scripts
function Find-UnsignedScripts {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[array]$LogonScripts
)
$results = @()
foreach ($script in $LogonScripts) {
# Only check PowerShell scripts for signatures
if ($script.Extension -eq '.ps1') {
try {
$signature = Get-AuthenticodeSignature -FilePath $script.FullName -ErrorAction Stop
if ($signature.Status -ne 'Valid') {
$results += [pscustomobject]@{
Type = 'UnsignedScript'
File = $script.FullName
Status = $signature.Status
}
}
} catch {
Write-Warning "Could not check signature for: $($script.FullName) - $($_.Exception.Message)"
}
}
}
return $results
}
# Check for sensitive information
function Find-SensitiveInformation {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[array]$LogonScripts
)
$SensitivePatterns = @(
'apikey\s*=\s*[''"].*[''"]',
'token\s*=\s*[''"].*[''"]',
'password\s*=\s*[''"].*[''"]',
'secret\s*=\s*[''"].*[''"]',
'connectionstring.*password',
'pwd\s*=',
'pass\s*='
)
$results = @()
foreach ($script in $LogonScripts) {
try {
$content = Get-Content -Path $script.FullName -Raw -ErrorAction Stop
foreach ($pattern in $SensitivePatterns) {
$matches = [regex]::Matches($content, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
foreach ($match in $matches) {
$results += [pscustomobject]@{
Type = 'SensitiveInformation'
File = $script.FullName
Match = $match.Value.Substring(0, [Math]::Min(50, $match.Value.Length)) + "..."
}
}
}
} catch {
Write-Warning "Could not read script: $($script.FullName) - $($_.Exception.Message)"
}
}
return $results
}
# Check for insecure practices
function Find-InsecurePractices {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[array]$LogonScripts
)
$InsecurePatterns = @(
'net use .* /user:.*',
'cmd /c .*',
'echo.*password',
'type.*password',
'reg add.*password'
)
$results = @()
foreach ($script in $LogonScripts) {
try {
$content = Get-Content -Path $script.FullName -Raw -ErrorAction Stop
foreach ($pattern in $InsecurePatterns) {
if ($content -match $pattern) {
$results += [pscustomobject]@{
Type = 'InsecurePractice'
File = $script.FullName
Pattern = $pattern
}
}
}
} catch {
Write-Warning "Could not read script: $($script.FullName) - $($_.Exception.Message)"
}
}
return $results
}
# Check for known vulnerable scripts
function Find-KnownVulnerableScripts {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[array]$LogonScripts
)
$VulnerablePatterns = @(
'Invoke-Mimikatz',
'Invoke-Expression.*\$env:',
'Start-Process.*cmd\.exe.*/c',
'powershell.*-enc',
'bypass.*executionpolicy'
)
$results = @()
foreach ($script in $LogonScripts) {
try {
$content = Get-Content -Path $script.FullName -Raw -ErrorAction Stop
foreach ($pattern in $VulnerablePatterns) {
if ($content -match $pattern) {
$results += [pscustomobject]@{
Type = 'KnownVulnerableScript'
File = $script.FullName
Pattern = $pattern
}
}
}
} catch {
Write-Warning "Could not read script: $($script.FullName) - $($_.Exception.Message)"
}
}
return $results
}
# Check for scripts in non-standard locations
function Find-NonStandardScriptLocations {
[CmdletBinding()]
param()
$results = @()
try {
$users = Get-ADUser -Filter 'scriptPath -like "*"' -Properties scriptPath -ErrorAction Stop
foreach ($user in $users) {
$scriptPath = $user.scriptPath
if ($scriptPath -and $scriptPath -like '\\*') {
$parts = $scriptPath -split '\\'
if ($parts.Count -ge 3) {
$share = $parts[2]
if ($share -ne 'NETLOGON' -and $share -ne 'SYSVOL') {
$results += [pscustomobject]@{
Type = 'NonStandardScriptLocation'
User = $user.SamAccountName
ScriptPath = $scriptPath
}
}
}
}
}
} catch {
Write-Warning "Could not query AD users: $($_.Exception.Message)"
}
return $results
}
# Check for unsafe GPO script settings
function Find-UnsafeGPOScriptSettings {
[CmdletBinding()]
param()
$results = @()
try {
$GPOs = Get-GPO -All -ErrorAction Stop
foreach ($GPO in $GPOs) {
try {
$policy = Get-GPRegistryValue -Guid $GPO.Id -Key 'HKLM\Software\Policies\Microsoft\Windows\PowerShell' -ValueName 'ExecutionPolicy' -ErrorAction SilentlyContinue
if ($policy -and $policy.Value -eq 'Unrestricted') {
$results += [pscustomobject]@{
Type = 'UnsafeGPOScriptSetting'
GPO = $GPO.DisplayName
Setting = 'ExecutionPolicy = Unrestricted'
}
}
} catch {
# Silently continue if registry value doesn't exist
}
}
} catch {
Write-Warning "Could not query GPOs: $($_.Exception.Message)"
}
return $results
}
# Function to check for threat intelligence
function Invoke-ThreatIntelligenceCheck {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Content,
[Parameter(Mandatory = $true)]
[string]$ApiKey
)
if ([string]::IsNullOrEmpty($ApiKey) -or $ApiKey -eq "your_virus_total_api_key_here") {
Write-Verbose "No valid VirusTotal API key provided, skipping threat intelligence check"
return "No API key provided"
}
try {
# Compute SHA256 hash of the content
$hasher = [System.Security.Cryptography.SHA256]::Create()
$contentBytes = [System.Text.Encoding]::UTF8.GetBytes($Content)
$hashBytes = $hasher.ComputeHash($contentBytes)
$hash = [System.BitConverter]::ToString($hashBytes).Replace("-", "").ToLower()
$hasher.Dispose()
# Query VirusTotal API
$uri = "https://www.virustotal.com/api/v3/files/$hash"
$headers = @{
"x-apikey" = $ApiKey
}
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers -TimeoutSec 30 -ErrorAction Stop
# Check if the file is flagged as malicious
if ($response.data.attributes.last_analysis_stats.malicious -gt 0) {
return "Threat detected: $($response.data.attributes.last_analysis_stats.malicious) engines flagged this as malicious"
} else {
return "No threat detected"
}
} catch {
Write-Verbose "Error querying VirusTotal: $($_.Exception.Message)"
return "Error querying VirusTotal: $($_.Exception.Message)"
}
}
# Function to detect obfuscated code
function Find-ObfuscatedScripts {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[array]$LogonScripts
)
$ObfuscationPatterns = @(
'FromBase64String',
'Invoke-Expression',
'IEX\s*\(',
'EncodedCommand',
'-enc\s+',
'Convert.*FromBase64',
'System\.Convert::FromBase64String'
)
$results = @()
foreach ($script in $LogonScripts) {
try {
$content = Get-Content -Path $script.FullName -Raw -ErrorAction Stop
foreach ($pattern in $ObfuscationPatterns) {
if ($content -match $pattern) {
$results += [pscustomobject]@{
Type = 'ObfuscatedCode'
File = $script.FullName
Pattern = $pattern
}
}
}
} catch {
Write-Warning "Could not read script: $($script.FullName) - $($_.Exception.Message)"
}
}
return $results
}
# Function to validate script paths
function Validate-ScriptPaths {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[array]$LogonScripts
)
$results = @()
foreach ($script in $LogonScripts) {
try {
$content = Get-Content -Path $script.FullName -Raw -ErrorAction Stop
$paths = [regex]::Matches($content, '\\\\[\w\.\-]+\\[\w\-_\\.\\]+')
foreach ($path in $paths) {
if (-not (Test-Path $path.Value -ErrorAction SilentlyContinue)) {
$results += [pscustomobject]@{
Type = 'InvalidPath'
File = $script.FullName
Path = $path.Value
}
}
}
} catch {
Write-Warning "Could not read script: $($script.FullName) - $($_.Exception.Message)"
}
}
return $results
}
# Function to check for external resource references
function Find-ExternalResourceReferences {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[array]$LogonScripts
)
$ExternalPatterns = @(
'https?://[^\s]+',
'ftp://[^\s]+',
'\b(?:\d{1,3}\.){3}\d{1,3}\b'
)
$results = @()
foreach ($script in $LogonScripts) {
try {
$content = Get-Content -Path $script.FullName -Raw -ErrorAction Stop
foreach ($pattern in $ExternalPatterns) {
$matches = [regex]::Matches($content, $pattern)
foreach ($match in $matches) {
$results += [pscustomobject]@{
Type = 'ExternalResource'
File = $script.FullName
Reference = $match.Value
}
}
}
} catch {
Write-Warning "Could not read script: $($script.FullName) - $($_.Exception.Message)"
}
}
return $results
}
# Function to provide remediation suggestions
function Get-RemediationSuggestions {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$IssueType
)
$RemediationTable = @{
'ElevatedScript' = 'Ensure scripts do not run with unnecessary elevated privileges. Use least privilege principles.'
'MaliciousCode' = 'Remove or replace malicious code patterns. Use trusted code only.'
'UnsignedScript' = 'Sign scripts with a trusted certificate to ensure integrity.'
'SensitiveInformation' = 'Remove hardcoded sensitive information. Use secure storage or credential prompts.'
'InsecurePractice' = 'Avoid insecure commands like net use with credentials or cmd /c. Use secure alternatives.'
'KnownVulnerableScript' = 'Update or remove scripts with known vulnerabilities. Apply patches or use secure versions.'
'NonStandardScriptLocation' = 'Move scripts to standard locations like NETLOGON or SYSVOL for better management and security.'
'UnsafeGPOScriptSetting' = 'Set PowerShell execution policy to a more restrictive setting like RemoteSigned or AllSigned.'
'ObfuscatedCode' = 'Review and deobfuscate the script to ensure it does not contain malicious code. Consider replacing with clear, readable code.'
'InvalidPath' = 'Verify and correct the invalid path in the script. Ensure the path exists and is accessible.'
'ExternalResource' = 'Validate the necessity of external resources. Use trusted sources or replace with local alternatives if possible.'
'ThreatDetected' = 'Quarantine the script and investigate the threat. Remove or replace the malicious content.'
'AccessDenied' = 'Grant the account running the script read permissions to the affected path (e.g., SYSVOL or NETLOGON). Verify NTFS and share permissions.'
}
return $RemediationTable[$IssueType]
}
# Enhanced function to show results in a professional GUI - IMPROVED LAYOUT
function Show-Results {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[array]$Results
)
# Create main form
$form = New-Object System.Windows.Forms.Form
$form.Text = 'ScriptSentry Analysis Results'
$form.Size = New-Object System.Drawing.Size(1200, 900)
$form.StartPosition = 'CenterScreen'
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$form.MaximizeBox = $false
$form.MinimizeBox = $false
# Use TableLayoutPanel for a more organized layout
$mainLayout = New-Object System.Windows.Forms.TableLayoutPanel
$mainLayout.Dock = 'Fill'
$mainLayout.ColumnCount = 1
$mainLayout.RowCount = 4 # Header, Filter, ListView, Footer
$mainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 60))) # Header
$mainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 40))) # Filter
$mainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) # ListView
$mainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 50))) # Footer (for close button)
# Header panel
$headerPanel = New-Object System.Windows.Forms.Panel
$headerPanel.Dock = 'Fill'
$headerPanel.BackColor = [System.Drawing.Color]::FromArgb(45, 45, 48)
# Title label
$titleLabel = New-Object System.Windows.Forms.Label
$titleLabel.Text = 'ScriptSentry Security Analysis Report'
$titleLabel.Font = New-Object System.Drawing.Font('Segoe UI', 16, [System.Drawing.FontStyle]::Bold)
$titleLabel.ForeColor = [System.Drawing.Color]::White
$titleLabel.AutoSize = $true
$titleLabel.Location = New-Object System.Drawing.Point(20, 15)
$headerPanel.Controls.Add($titleLabel)
# Subtitle label - now shows total issues
$subtitleLabel = New-Object System.Windows.Forms.Label
$subtitleLabel.Text = "Found $($Results.Count) security issues"
$subtitleLabel.Font = New-Object System.Drawing.Font('Segoe UI', 10)
$subtitleLabel.ForeColor = [System.Drawing.Color]::LightGray
$subtitleLabel.AutoSize = $true
$subtitleLabel.Location = New-Object System.Drawing.Point(25, 40)
$headerPanel.Controls.Add($subtitleLabel)
# Filter panel
$filterPanel = New-Object System.Windows.Forms.Panel
$filterPanel.Dock = 'Fill'
$filterPanel.BackColor = [System.Drawing.Color]::FromArgb(240, 240, 240)
# Filter label
$filterLabel = New-Object System.Windows.Forms.Label
$filterLabel.Text = 'Filter by type:'
$filterLabel.Font = New-Object System.Drawing.Font('Segoe UI', 9)
$filterLabel.AutoSize = $true
$filterLabel.Location = New-Object System.Drawing.Point(10, 12)
$filterPanel.Controls.Add($filterLabel)
# Filter combobox
$filterComboBox = New-Object System.Windows.Forms.ComboBox
$filterComboBox.Width = 200
$filterComboBox.Location = New-Object System.Drawing.Point(90, 10)
# Get unique issue types
$issueTypes = $Results | Select-Object -ExpandProperty Type -Unique | Sort-Object
$filterComboBox.Items.Add('All Issues') | Out-Null
foreach ($type in $issueTypes) {
$filterComboBox.Items.Add($type) | Out-Null
}
$filterComboBox.SelectedIndex = 0
$filterPanel.Controls.Add($filterComboBox)
# Export button
$exportButton = New-Object System.Windows.Forms.Button
$exportButton.Text = 'Export Results'
$exportButton.Width = 120
$exportButton.Location = New-Object System.Drawing.Point(1050, 10)
$exportButton.BackColor = [System.Drawing.Color]::FromArgb(0, 120, 215)
$exportButton.ForeColor = [System.Drawing.Color]::White
$exportButton.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$filterPanel.Controls.Add($exportButton)
# ListView
$listView = New-Object System.Windows.Forms.ListView
$listView.Dock = 'Fill'
$listView.View = 'Details'
$listView.FullRowSelect = $true
$listView.GridLines = $true
$listView.MultiSelect = $false
$listView.Columns.Add('Type', 150) | Out-Null
$listView.Columns.Add('File/Path', 350) | Out-Null
$listView.Columns.Add('Details', 250) | Out-Null
$listView.Columns.Add('Remediation', 400) | Out-Null
# Footer panel
$footerPanel = New-Object System.Windows.Forms.Panel
$footerPanel.Dock = 'Fill'
$footerPanel.Height = 50
# Close button
$closeButton = New-Object System.Windows.Forms.Button
$closeButton.Text = 'Close'
$closeButton.Width = 100
$closeButton.Height = 30
$closeButton.Location = New-Object System.Drawing.Point(1080, 10)
$closeButton.BackColor = [System.Drawing.Color]::FromArgb(200, 50, 50)
$closeButton.ForeColor = [System.Drawing.Color]::White
$closeButton.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$closeButton.Add_Click({ $form.Close() })
$footerPanel.Controls.Add($closeButton)
# Add controls to the layout panel
$mainLayout.Controls.Add($headerPanel, 0, 0)
$mainLayout.Controls.Add($filterPanel, 0, 1)
$mainLayout.Controls.Add($listView, 0, 2)
$mainLayout.Controls.Add($footerPanel, 0, 3)
# Add the layout panel to the form
$form.Controls.Add($mainLayout)
# Store original results for filtering
$originalResults = $Results
# Function to populate the ListView with results
function Populate-ListView {
param(
[array]$itemsToDisplay
)
$listView.Items.Clear()
foreach ($result in $itemsToDisplay) {
$item = New-Object System.Windows.Forms.ListViewItem($result.Type)
# Handle different property names for file/path
$fileOrPath = ""
if ($result.File) { $fileOrPath = $result.File }
elseif ($result.Path) { $fileOrPath = $result.Path }
elseif ($result.ScriptPath) { $fileOrPath = $result.ScriptPath }
elseif ($result.User) { $fileOrPath = $result.User }
elseif ($result.GPO) { $fileOrPath = $result.GPO }
$item.SubItems.Add($fileOrPath) | Out-Null
# Handle different property names for details
$details = ""
if ($result.Pattern) { $details = $result.Pattern }
elseif ($result.Match) { $details = $result.Match }
elseif ($result.Reference) { $details = $result.Reference }
elseif ($result.Threat) { $details = $result.Threat }
elseif ($result.Error) { $details = $result.Error }
elseif ($result.Setting) { $details = $result.Setting }
elseif ($result.Status) { $details = $result.Status }
$item.SubItems.Add($details) | Out-Null
$item.SubItems.Add($result.Remediation) | Out-Null
$listView.Items.Add($item) | Out-Null
}
}
# Initial population of ListView
Populate-ListView -itemsToDisplay $originalResults
# Filter functionality
$filterComboBox.Add_SelectedIndexChanged({
$selectedType = $filterComboBox.SelectedItem
if ($selectedType -eq 'All Issues') {
Populate-ListView -itemsToDisplay $originalResults
} else {
$filteredResults = $originalResults | Where-Object { $_.Type -eq $selectedType }
Populate-ListView -itemsToDisplay $filteredResults
}
})
# Export functionality - ENHANCED WITH CSV SUPPORT
$exportButton.Add_Click({
$saveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
$saveFileDialog.Filter = "CSV files (*.csv)|*.csv|Text files (*.txt)|*.txt|All files (*.*)|*.*" # CSV first
$saveFileDialog.Title = "Export Results"
$saveFileDialog.FileName = "ScriptSentry_Report_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv" # Default to CSV
$saveFileDialog.FilterIndex = 1 # Default to CSV
if ($saveFileDialog.ShowDialog() -eq 'OK') {
try {
if ($saveFileDialog.FilterIndex -eq 1) { # CSV format
$originalResults | Export-Csv -Path $saveFileDialog.FileName -NoTypeInformation -Encoding UTF8
} else { # Text format
$originalResults | Format-Table -Property Type, @{Label='File/Path';Expression={
if ($_.File) { $_.File }
elseif ($_.Path) { $_.Path }
elseif ($_.ScriptPath) { $_.ScriptPath }
elseif ($_.User) { $_.User }
elseif ($_.GPO) { $_.GPO }
else { "" }
}}, @{Label='Details';Expression={
if ($_.Pattern) { $_.Pattern }
elseif ($_.Match) { $_.Match }
elseif ($_.Reference) { $_.Reference }
elseif ($_.Threat) { $_.Threat }
elseif ($_.Error) { $_.Error }
elseif ($_.Setting) { $_.Setting }
elseif ($_.Status) { $_.Status }
else { "" }
}}, Remediation -AutoSize | Out-File -FilePath $saveFileDialog.FileName -Encoding UTF8
}
[System.Windows.Forms.MessageBox]::Show("Results exported successfully to $($saveFileDialog.FileName)", "Export Complete", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
} catch {
[System.Windows.Forms.MessageBox]::Show("Failed to export results: $($_.Exception.Message)", "Export Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
}
})
# Show form
[void]$form.ShowDialog()
}
# Main execution
Write-Host "`nStarting ScriptSentry analysis..." -ForegroundColor Green
$LogonScripts, $AccessIssues = Get-LogonScripts
# Add remediation suggestions for access issues
foreach ($issue in $AccessIssues) {
$issue | Add-Member -MemberType NoteProperty -Name 'Remediation' -Value (Get-RemediationSuggestions -IssueType $issue.Type) -Force
}
$AllResults = @($AccessIssues)
if ($LogonScripts -and $LogonScripts.Count -gt 0) {
Write-Host "Found $($LogonScripts.Count) logon scripts. Analyzing..." -ForegroundColor Yellow
# Perform checks on logon scripts
Write-Host "`nChecking for elevated scripts..." -ForegroundColor Cyan
$ElevatedScripts = Find-ElevatedScripts -LogonScripts $LogonScripts
Write-Host "Checking for malicious code..." -ForegroundColor Cyan
$MaliciousCode = Find-MaliciousCode -LogonScripts $LogonScripts
Write-Host "Checking for unsigned scripts..." -ForegroundColor Cyan
$UnsignedScripts = Find-UnsignedScripts -LogonScripts $LogonScripts
Write-Host "Checking for sensitive information..." -ForegroundColor Cyan
$SensitiveInfo = Find-SensitiveInformation -LogonScripts $LogonScripts
Write-Host "Checking for insecure practices..." -ForegroundColor Cyan
$InsecurePractices = Find-InsecurePractices -LogonScripts $LogonScripts
Write-Host "Checking for known vulnerable scripts..." -ForegroundColor Cyan
$KnownVulnerableScripts = Find-KnownVulnerableScripts -LogonScripts $LogonScripts
Write-Host "Checking for obfuscated scripts..." -ForegroundColor Cyan
$ObfuscatedScripts = Find-ObfuscatedScripts -LogonScripts $LogonScripts
Write-Host "Validating script paths..." -ForegroundColor Cyan
$InvalidPaths = Validate-ScriptPaths -LogonScripts $LogonScripts
Write-Host "Checking for external resource references..." -ForegroundColor Cyan
$ExternalResources = Find-ExternalResourceReferences -LogonScripts $LogonScripts
# Integrate threat intelligence if API key is provided
$ThreatDetectedScripts = @()
if (-not [string]::IsNullOrEmpty($VTApiKey) -and $VTApiKey -ne "your_virus_total_api_key_here") {
Write-Host "Running threat intelligence checks..." -ForegroundColor Cyan
foreach ($script in $LogonScripts) {
try {
$content = Get-Content -Path $script.FullName -Raw -ErrorAction Stop
$threatResult = Invoke-ThreatIntelligenceCheck -Content $content -ApiKey $VTApiKey
if ($threatResult -ne "No threat detected" -and $threatResult -ne "No API key provided") {
$ThreatDetectedScripts += [pscustomobject]@{
Type = 'ThreatDetected'
File = $script.FullName
Threat = $threatResult
}
}
} catch {
Write-Warning "Could not read script for threat analysis: $($script.FullName)"
}
}
}
# Perform additional checks
Write-Host "Checking for non-standard script locations..." -ForegroundColor Cyan
$NonStandardLocations = Find-NonStandardScriptLocations
Write-Host "Checking for unsafe GPO settings..." -ForegroundColor Cyan
$UnsafeGPOSettings = Find-UnsafeGPOScriptSettings
# Collect all results and filter out empty arrays
$AllChecks = @($ElevatedScripts, $MaliciousCode, $UnsignedScripts, $SensitiveInfo, $InsecurePractices, $KnownVulnerableScripts, $ObfuscatedScripts, $InvalidPaths, $ExternalResources, $ThreatDetectedScripts, $NonStandardLocations, $UnsafeGPOSettings)
foreach ($check in $AllChecks) {
if ($check -and $check.Count -gt 0) {
$AllResults += $check
}
}
} else {
Write-Host "No logon scripts found to analyze." -ForegroundColor Yellow
}
# Add remediation suggestions for all results
foreach ($result in $AllResults) {
if (-not $result.Remediation) {
$remediation = Get-RemediationSuggestions -IssueType $result.Type
$result | Add-Member -MemberType NoteProperty -Name 'Remediation' -Value $remediation -Force
}
}
# Display results
if ($AllResults -and $AllResults.Count -gt 0) {
Write-Host "`nAnalysis complete. Found $($AllResults.Count) issues." -ForegroundColor Red
Show-Results $AllResults
} else {
Write-Host "`nNo issues detected in the analyzed scripts." -ForegroundColor Green
[System.Windows.Forms.MessageBox]::Show("No issues detected in the analyzed scripts.", "ScriptSentry Results", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
}
# The summary table feature is now removed from here.
# Optionally save output
if ($SaveOutput -and $AllResults -and $AllResults.Count -gt 0) {
$OutputFile = "ScriptSentry_Report_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
try {
$AllResults | Format-Table -Property Type, @{Label='File/Path';Expression={
if ($_.File) { $_.File }
elseif ($_.Path) { $_.Path }
elseif ($_.ScriptPath) { $_.ScriptPath }
elseif ($_.User) { $_.User }
elseif ($_.GPO) { $_.GPO }
else { "" }
}}, @{Label='Details';Expression={
if ($_.Pattern) { $_.Pattern }
elseif ($_.Match) { $_.Match }
elseif ($_.Reference) { $_.Reference }
elseif ($_.Threat) { $_.Threat }
elseif ($_.Error) { $_.Error }
elseif ($_.Setting) { $_.Setting }
elseif ($_.Status) { $_.Status }
else { "" }
}}, Remediation -AutoSize | Out-File -FilePath $OutputFile -Encoding UTF8
Write-Host "`nResults saved to $OutputFile" -ForegroundColor Green
} catch {
Write-Error "Failed to save output: $($_.Exception.Message)"
}
}
# Display completion message as required
Write-Host "`n=== ScriptSentry Analysis Completed ===" -ForegroundColor Green
# Return results only if not running in GUI mode
# This prevents console output after closing the GUI
if ($AllResults -and $AllResults.Count -gt 0) {
# No return here, as the script should continue to display the summary table
} else {
# Original return for no results
return $AllResults
}
}
# Example usage with parameter validation
if ($MyInvocation.InvocationName -ne '.') {
# Only run if script is executed directly, not dot-sourced
try {
# Capture results in $null to prevent console output
$null = Invoke-ScriptSentry -SaveOutput $true -Verbose
} catch {
Write-Error "Failed to run ScriptSentry: $($_.Exception.Message)"
}
}
Metadata
Metadata
Assignees
Labels
No labels