Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions AppLens-Tune.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
.DESCRIPTION
Captures a targeted workstation snapshot focused on startup load, background
services, local AI tooling, storage hotspots, and repo placement. The
script does not change the machine. It only writes a plain-text report.
script does not change the machine. It only writes a Markdown report.
#>

function Get-DesktopFilePath {
Expand All @@ -14,7 +14,10 @@ function Get-DesktopFilePath {
[string]$FileName
)

$desktopPath = [Environment]::GetFolderPath('Desktop')
$desktopPath = $env:APPLENS_OUTPUT_DIR
if ([string]::IsNullOrWhiteSpace($desktopPath)) {
$desktopPath = [Environment]::GetFolderPath('Desktop')
}
if ([string]::IsNullOrWhiteSpace($desktopPath)) {
$desktopPath = Join-Path $env:USERPROFILE 'Desktop'
}
Expand Down Expand Up @@ -207,7 +210,8 @@ function New-Section {

$section = @()
$section += ''
$section += $Title
$heading = $Title -replace '^\s*---\s*', '' -replace '\s*---\s*$', ''
$section += "## $heading"
if (-not $Lines -or $Lines.Count -eq 0) {
$section += '(none)'
} else {
Expand All @@ -216,7 +220,7 @@ function New-Section {
return $section
}

$OutputPath = Get-DesktopFilePath -FileName "AppLens_Tune_Results_$env:COMPUTERNAME.txt"
$OutputPath = Get-DesktopFilePath -FileName "AppLens_Tune_Results_$env:COMPUTERNAME.md"

$computerSystem = Get-CimInstance Win32_ComputerSystem
$operatingSystem = Get-CimInstance Win32_OperatingSystem
Expand Down Expand Up @@ -407,18 +411,18 @@ if ($cDrive -and $cDrive.Free -lt 100GB) {
}

$summaryLines = @(
"Machine: $($computerSystem.Manufacturer) $($computerSystem.Model)",
"OS: $($operatingSystem.Caption) ($($operatingSystem.Version))",
"RAM: $('{0:N1} GB' -f ($computerSystem.TotalPhysicalMemory / 1GB))",
"C: Free: $(Format-Size $cDrive.Free)"
"- **Machine:** $($computerSystem.Manufacturer) $($computerSystem.Model)",
"- **OS:** $($operatingSystem.Caption) ($($operatingSystem.Version))",
"- **RAM:** $('{0:N1} GB' -f ($computerSystem.TotalPhysicalMemory / 1GB))",
"- **C: Free:** $(Format-Size $cDrive.Free)"
)

$output = @()
$output += '=== AppLens-Tune Audit Results ==='
$output += "Computer: $env:COMPUTERNAME"
$output += "User: $env:USERNAME"
$output += "Scan Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
$output += 'Mode: Audit (read-only)'
$output += '# AppLens-Tune Audit Results'
$output += "- **Computer:** $env:COMPUTERNAME"
$output += "- **User:** $env:USERNAME"
$output += "- **Scan Date:** $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
$output += '- **Mode:** Audit (read-only)'
$output += ''
$output += $summaryLines
$output += New-Section -Title '--- Stability Checks ---' -Lines $stableFindings
Expand Down
23 changes: 12 additions & 11 deletions AppLens-Tune.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ def table(rows: list[dict[str, object]], columns: list[str]) -> list[str]:


def section(title: str, lines: list[str]) -> list[str]:
return ["", title, *(lines if lines else ["(none)"])]
heading = title.strip().strip("-").strip()
return ["", f"## {heading}", *(lines if lines else ["(none)"])]


def total_ram_bytes() -> int | None:
Expand Down Expand Up @@ -638,16 +639,16 @@ def build_report() -> str:
stable, review, optional = build_findings(startup_rows, service_rows, storage_rows, repo_rows, llm_review, llm_optional)

lines: list[str] = []
lines.append("=== AppLens-Tune Audit Results ===")
lines.append(f"Computer: {socket.gethostname()}")
lines.append(f"User: {getpass.getuser()}")
lines.append(f"Scan Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
lines.append("Mode: Audit (read-only)")
lines.append("# AppLens-Tune Audit Results")
lines.append(f"- **Computer:** {socket.gethostname()}")
lines.append(f"- **User:** {getpass.getuser()}")
lines.append(f"- **Scan Date:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
lines.append("- **Mode:** Audit (read-only)")
lines.append("")
lines.append(f"Machine: {platform.machine()}")
lines.append(f"OS: {platform.platform()}")
lines.append(f"RAM: {format_size(total_ram_bytes())}")
lines.append(f"Root Free: {format_size(disk.free)}")
lines.append(f"- **Machine:** {platform.machine()}")
lines.append(f"- **OS:** {platform.platform()}")
lines.append(f"- **RAM:** {format_size(total_ram_bytes())}")
lines.append(f"- **Root Free:** {format_size(disk.free)}")
lines.extend(section("--- Stability Checks ---", stable))
lines.extend(section("--- Review Items ---", review))
lines.extend(section("--- Optional Improvements ---", optional))
Expand All @@ -670,7 +671,7 @@ def build_report() -> str:

def main() -> int:
report = build_report()
path = output_path(f"AppLens_Tune_Results_{socket.gethostname()}.txt")
path = output_path(f"AppLens_Tune_Results_{socket.gethostname()}.md")
path.write_text(report, encoding="utf-8")
print(report, end="")
print("")
Expand Down
25 changes: 14 additions & 11 deletions AppLens.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
AppLens — Pre-Audit App Scanner for CSI AI Workflow Audits
.DESCRIPTION
Scans installed desktop apps (Win32) and Microsoft Store apps without
requiring admin rights. Outputs a clean, categorized text file suitable
requiring admin rights. Outputs a clean, categorized Markdown file suitable
for pasting into an AI prompt alongside a workflow audit transcript.
#>

Expand All @@ -16,7 +16,10 @@ function Get-DesktopFilePath {
[string]$FileName
)

$desktopPath = [Environment]::GetFolderPath('Desktop')
$desktopPath = $env:APPLENS_OUTPUT_DIR
if ([string]::IsNullOrWhiteSpace($desktopPath)) {
$desktopPath = [Environment]::GetFolderPath('Desktop')
}
if ([string]::IsNullOrWhiteSpace($desktopPath)) {
$desktopPath = Join-Path $env:USERPROFILE 'Desktop'
}
Expand All @@ -28,7 +31,7 @@ function Get-DesktopFilePath {
return Join-Path $desktopPath $FileName
}

$OutputPath = Get-DesktopFilePath -FileName "AppLens_Results_$env:COMPUTERNAME.txt"
$OutputPath = Get-DesktopFilePath -FileName "AppLens_Results_$env:COMPUTERNAME.md"

# Patterns for apps that should be filtered into the "Runtimes & Frameworks" section
$RuntimePatterns = @(
Expand Down Expand Up @@ -425,17 +428,17 @@ $storeApps = Get-StoreApps

# Build output
$output = @()
$output += "=== AppLens Scan Results ==="
$output += "Computer: $env:COMPUTERNAME"
$output += "User: $env:USERNAME"
$output += "Scan Date: $(Get-Date -Format 'yyyy-MM-dd')"
$output += "# AppLens Scan Results"
$output += "- **Computer:** $env:COMPUTERNAME"
$output += "- **User:** $env:USERNAME"
$output += "- **Scan Date:** $(Get-Date -Format 'yyyy-MM-dd')"
$output += ""
$output += "--- Desktop Applications ---"
$output += "## Desktop Applications"

# Microsoft 365 group
if ($m365Apps.Count -gt 0) {
$m365Apps = $m365Apps | Sort-Object Name
$output += "Microsoft 365 (Office)"
$output += "### Microsoft 365 (Office)"
foreach ($app in $m365Apps) {
$shortName = $app.Name
$output += Format-AppLine -Name $shortName -Version $app.Version -UserInstalled $app.UserInstalled -Indent ' - '
Expand All @@ -450,7 +453,7 @@ foreach ($app in $desktopApps) {

# Store apps
$output += ""
$output += "--- Microsoft Store Apps ---"
$output += "## Microsoft Store Apps"
if ($storeApps.Count -eq 0) {
$output += "(none detected)"
} else {
Expand All @@ -461,7 +464,7 @@ if ($storeApps.Count -eq 0) {

# Runtimes
$output += ""
$output += "--- Runtimes & Frameworks (for reference) ---"
$output += "## Runtimes & Frameworks (for reference)"
if ($runtimes.Count -eq 0) {
$output += "(none detected)"
} else {
Expand Down
22 changes: 11 additions & 11 deletions AppLens.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""
AppLens app inventory scanner for macOS and Linux.

Read-only. Writes a categorized plain-text report to the user's Desktop when
Read-only. Writes a categorized Markdown report to the user's Desktop when
available, or to the home directory when no Desktop folder exists.
"""

Expand Down Expand Up @@ -264,36 +264,36 @@ def build_report() -> str:
package_apps = [item for item in package_apps if not is_runtime(item["name"])]

lines: list[str] = []
lines.append("=== AppLens Scan Results ===")
lines.append(f"Computer: {socket.gethostname()}")
lines.append(f"User: {getpass.getuser()}")
lines.append(f"OS: {platform.platform()}")
lines.append(f"Scan Date: {datetime.now().strftime('%Y-%m-%d')}")
lines.append("# AppLens Scan Results")
lines.append(f"- **Computer:** {socket.gethostname()}")
lines.append(f"- **User:** {getpass.getuser()}")
lines.append(f"- **OS:** {platform.platform()}")
lines.append(f"- **Scan Date:** {datetime.now().strftime('%Y-%m-%d')}")
lines.append("")
lines.append("--- Desktop Applications ---")
lines.append("## Desktop Applications")
desktop_apps = dedupe(desktop_apps)
if desktop_apps:
lines.extend(format_app(**item) for item in desktop_apps)
else:
lines.append("(none detected)")

lines.append("")
lines.append(package_title)
lines.append(f"## {package_title.strip('- ')}")
package_apps = dedupe(package_apps)
if package_apps:
lines.extend(format_app(**item) for item in package_apps)
else:
lines.append("(none detected)")

lines.append("")
lines.append("--- Developer/CLI Tools (detected) ---")
lines.append("## Developer/CLI Tools (detected)")
if developer_tools:
lines.extend(format_app(**item) for item in developer_tools)
else:
lines.append("(none detected)")

lines.append("")
lines.append("--- Runtimes & Frameworks (for reference) ---")
lines.append("## Runtimes & Frameworks (for reference)")
runtimes = dedupe(runtimes)
if runtimes:
lines.extend(format_app(**item) for item in runtimes)
Expand All @@ -305,7 +305,7 @@ def build_report() -> str:

def main() -> int:
report = build_report()
path = output_path(f"AppLens_Results_{socket.gethostname()}.txt")
path = output_path(f"AppLens_Results_{socket.gethostname()}.md")
path.write_text(report, encoding="utf-8")
print(report, end="")
print("")
Expand Down
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ More detail is in [docs/AppLensDesktop-Build.md](docs/AppLensDesktop-Build.md),

Double-click:

```text
Run-AppLens-Capture.bat
```

This writes both reports and logs to a Desktop folder named `AppLens-Capture-<ComputerName>`.

Individual scripts:

```text
Run-AppLens.bat
Run-AppLens-Tune.bat
Expand All @@ -84,9 +92,16 @@ powershell -ExecutionPolicy Bypass -File AppLens-Tune.ps1
### macOS and Linux

```sh
chmod +x Run-AppLens.sh Run-AppLens-Tune.sh
./Run-AppLens.sh
./Run-AppLens-Tune.sh
sh Run-AppLens-Capture.sh
```

This writes both reports and logs to a Desktop folder named `AppLens-Capture-<ComputerName>`.

Individual scripts:

```sh
sh Run-AppLens.sh
sh Run-AppLens-Tune.sh
```

Or run Python directly:
Expand All @@ -100,8 +115,8 @@ python3 AppLens-Tune.py

Script reports are written to the user's Desktop:

- `AppLens_Results_<ComputerName>.txt`
- `AppLens_Tune_Results_<ComputerName>.txt`
- `AppLens_Results_<ComputerName>.md`
- `AppLens_Tune_Results_<ComputerName>.md`

The desktop app exports:

Expand Down
69 changes: 69 additions & 0 deletions Run-AppLens-Capture.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
@echo off
setlocal EnableExtensions

set APPLENS_INTERACTIVE=0
set "CAPTURE_DIR=%USERPROFILE%\Desktop\AppLens-Capture-%COMPUTERNAME%"
set "APPLENS_OUTPUT_DIR=%CAPTURE_DIR%"
set "CAPTURE_EXIT=0"

if not exist "%CAPTURE_DIR%" mkdir "%CAPTURE_DIR%"
if exist "%CAPTURE_DIR%\AppLens_Results_%COMPUTERNAME%.txt" del /q "%CAPTURE_DIR%\AppLens_Results_%COMPUTERNAME%.txt"
if exist "%CAPTURE_DIR%\AppLens_Tune_Results_%COMPUTERNAME%.txt" del /q "%CAPTURE_DIR%\AppLens_Tune_Results_%COMPUTERNAME%.txt"
if exist "%CAPTURE_DIR%\README-What-To-Send.txt" del /q "%CAPTURE_DIR%\README-What-To-Send.txt"

echo AppLens Capture
echo.
echo This will run AppLens and AppLens-Tune, then open the output folder.
echo Output folder:
echo %CAPTURE_DIR%
echo.

call :run_script "AppLens" "AppLens.ps1" "AppLens_Run_Log.txt"
call :run_script "AppLens-Tune" "AppLens-Tune.ps1" "AppLens_Tune_Run_Log.txt"

> "%CAPTURE_DIR%\README-What-To-Send.md" echo # AppLens Capture
>>"%CAPTURE_DIR%\README-What-To-Send.md" echo.
>>"%CAPTURE_DIR%\README-What-To-Send.md" echo Send this entire folder back for AppLens intake.
>>"%CAPTURE_DIR%\README-What-To-Send.md" echo.
>>"%CAPTURE_DIR%\README-What-To-Send.md" echo ## Expected report files
>>"%CAPTURE_DIR%\README-What-To-Send.md" echo.
>>"%CAPTURE_DIR%\README-What-To-Send.md" echo - AppLens_Results_%COMPUTERNAME%.md
>>"%CAPTURE_DIR%\README-What-To-Send.md" echo - AppLens_Tune_Results_%COMPUTERNAME%.md
>>"%CAPTURE_DIR%\README-What-To-Send.md" echo.
>>"%CAPTURE_DIR%\README-What-To-Send.md" echo Include the log files if either report is missing.
>>"%CAPTURE_DIR%\README-What-To-Send.md" echo Do not include serial numbers or UUIDs unless explicitly requested.

echo.
if "%CAPTURE_EXIT%"=="0" (
echo Capture finished.
) else (
echo Capture finished with at least one issue. Include the log files.
)
echo.
echo Opening output folder...
start "" "%CAPTURE_DIR%"
echo.
echo Press any key to close.
pause >nul
exit /b %CAPTURE_EXIT%

:run_script
set "APP_NAME=%~1"
set "SCRIPT_NAME=%~2"
set "LOG_NAME=%~3"
set "LOG_PATH=%CAPTURE_DIR%\%LOG_NAME%"

echo Running %APP_NAME%...
echo [%DATE% %TIME%] Running %SCRIPT_NAME%>"%LOG_PATH%"
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0%SCRIPT_NAME%" >>"%LOG_PATH%" 2>&1
set "SCRIPT_EXIT=0"
if errorlevel 1 set "SCRIPT_EXIT=1"
>>"%LOG_PATH%" echo [%DATE% %TIME%] Exit code: %SCRIPT_EXIT%

if "%SCRIPT_EXIT%"=="0" (
echo %APP_NAME% finished.
) else (
echo %APP_NAME% had an issue. See %LOG_PATH%
set "CAPTURE_EXIT=1"
)
exit /b 0
Loading
Loading