Загрузка данных


#Requires -RunAsAdministrator
<#
.SYNOPSIS
  Creates a folder with internet shortcuts (.url) to the allowed sites on the desktop of a user.
  Run it as administrator; the shortcuts are written into the other user's profile.

.DESCRIPTION
  - Sites are grouped into sub-folders (Search, Docs, Languages, Frameworks, Tools, Learning, Messengers).
  - Each site is checked against the DevProxy allow-list (C:\ProgramData\DevProxy\allowlist.txt).
    A site whose host is NOT allowed is skipped (a dead shortcut is useless); use -IncludeBlocked to keep them.
  - The folder is rebuilt on every run: old .url files in it are removed first, so you can edit the list below
    and run the script again.
  - The user must have signed in at least once (the profile must exist).

.EXAMPLE
  .\create-site-shortcuts.ps1 -UserName german
  .\create-site-shortcuts.ps1 -UserName german -FolderName "Sites"
  .\create-site-shortcuts.ps1 -UserName german -IncludeBlocked

  Run in Windows PowerShell as Administrator:
  powershell -ExecutionPolicy Bypass -File .\create-site-shortcuts.ps1 -UserName german
  (A Russian folder name can be passed as -FolderName; the script file itself stays ASCII.)
#>
[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [string]$UserName,
    [string]$FolderName = 'Sites',
    [string]$AllowFile = (Join-Path $env:ProgramData 'DevProxy\allowlist.txt'),
    [switch]$IncludeBlocked
)

$ErrorActionPreference = 'Stop'

# ---------------- editable list: Category, Name, URL ----------------
$Sites = @(
    @('Search', 'Google', 'https://www.google.com'),
    @('Search', 'DuckDuckGo', 'https://duckduckgo.com'),

    @('Docs', 'MDN Web Docs', 'https://developer.mozilla.org'),
    @('Docs', 'Microsoft Learn', 'https://learn.microsoft.com'),
    @('Docs', 'DevDocs', 'https://devdocs.io'),
    @('Docs', 'W3Schools', 'https://www.w3schools.com'),
    @('Docs', 'web.dev', 'https://web.dev'),
    @('Docs', 'Can I use', 'https://caniuse.com'),
    @('Docs', 'regex101', 'https://regex101.com'),
    @('Docs', 'JSON', 'https://www.json.org'),

    @('Languages', 'Python docs', 'https://docs.python.org/3/'),
    @('Languages', 'PyPI', 'https://pypi.org'),
    @('Languages', 'Node.js docs', 'https://nodejs.org/docs/latest/api/'),
    @('Languages', 'npm', 'https://www.npmjs.com'),
    @('Languages', 'TypeScript', 'https://www.typescriptlang.org/docs/'),
    @('Languages', 'Go', 'https://go.dev/doc/'),
    @('Languages', 'Rust', 'https://www.rust-lang.org/learn'),
    @('Languages', 'PHP manual', 'https://www.php.net/manual/en/'),

    @('Frameworks', 'React', 'https://react.dev'),
    @('Frameworks', 'Vue', 'https://vuejs.org'),
    @('Frameworks', 'Angular', 'https://angular.dev'),
    @('Frameworks', 'Svelte', 'https://svelte.dev'),
    @('Frameworks', 'Next.js', 'https://nextjs.org/docs'),
    @('Frameworks', 'Vite', 'https://vite.dev'),
    @('Frameworks', 'Express', 'https://expressjs.com'),
    @('Frameworks', 'Tailwind CSS', 'https://tailwindcss.com/docs'),
    @('Frameworks', 'Bootstrap', 'https://getbootstrap.com/docs/'),
    @('Frameworks', 'Django', 'https://docs.djangoproject.com'),
    @('Frameworks', 'Flask', 'https://flask.palletsprojects.com'),
    @('Frameworks', 'FastAPI', 'https://fastapi.tiangolo.com'),

    @('Tools', 'GitHub', 'https://github.com'),
    @('Tools', 'Git docs', 'https://git-scm.com/doc'),
    @('Tools', 'VS Code docs', 'https://code.visualstudio.com/docs'),
    @('Tools', 'Stack Overflow', 'https://stackoverflow.com'),

    @('Learning', 'freeCodeCamp', 'https://www.freecodecamp.org'),
    @('Learning', 'javascript.info', 'https://javascript.info'),
    @('Learning', 'learn.javascript.ru', 'https://learn.javascript.ru'),
    @('Learning', 'Metanit', 'https://metanit.com'),

    @('Messengers', 'MAX', 'https://web.max.ru'),
    @('Messengers', 'VK', 'https://vk.com')
)

# ---------------- allow-list (same rules as the proxy) ----------------
$suffix = New-Object 'System.Collections.Generic.HashSet[string]'
$exact = New-Object 'System.Collections.Generic.HashSet[string]'
$haveAllowlist = Test-Path -LiteralPath $AllowFile
if ($haveAllowlist) {
    foreach ($line in (Get-Content -LiteralPath $AllowFile)) {
        $l = ($line -split '#')[0].Trim().ToLower()
        if (-not $l) { continue }
        if ($l.StartsWith('=')) { [void]$exact.Add($l.Substring(1).Trim().Trim('.')); continue }
        if ($l.StartsWith('*.')) { $l = $l.Substring(2) }
        [void]$suffix.Add($l.Trim('.'))
    }
}
else {
    Write-Warning "Allow-list not found ($AllowFile): shortcuts are created without checking."
}

function Test-HostAllowed {
    param([string]$HostName)
    if (-not $haveAllowlist) { return $true }
    $h = $HostName.ToLower().Trim('.')
    if ($exact.Contains($h)) { return $true }
    $labels = $h.Split('.')
    for ($i = 0; $i -lt $labels.Count; $i++) {
        $cand = ($labels[$i..($labels.Count - 1)]) -join '.'
        if ($suffix.Contains($cand)) { return $true }
    }
    return $false
}

# ---------------- locate the user's desktop ----------------
$u = Get-LocalUser -Name $UserName -ErrorAction SilentlyContinue
if (-not $u) { throw "Local user '$UserName' not found (check Get-LocalUser)" }
$prof = Get-CimInstance Win32_UserProfile -Filter "SID='$($u.SID.Value)'" -ErrorAction SilentlyContinue
if (-not $prof -or -not $prof.LocalPath) {
    throw "'$UserName' has no profile yet. Let him sign in once, then rerun."
}
$desktop = Join-Path $prof.LocalPath 'Desktop'
$oneDriveDesktop = Join-Path $prof.LocalPath 'OneDrive\Desktop'
if (-not (Test-Path -LiteralPath $desktop) -and (Test-Path -LiteralPath $oneDriveDesktop)) { $desktop = $oneDriveDesktop }
if (-not (Test-Path -LiteralPath $desktop)) { New-Item -ItemType Directory -Path $desktop -Force | Out-Null }

$root = Join-Path $desktop $FolderName
New-Item -ItemType Directory -Path $root -Force | Out-Null
Write-Host "Target folder: $root"

# rebuild: remove only old .url files
Get-ChildItem -LiteralPath $root -Recurse -Force -File -Filter '*.url' -ErrorAction SilentlyContinue |
    Remove-Item -Force -ErrorAction SilentlyContinue

# ---------------- create shortcuts ----------------
$created = 0
$skipped = @()
foreach ($s in $Sites) {
    $cat = $s[0]; $name = $s[1]; $url = $s[2]
    $hostName = ([uri]$url).Host
    if (-not (Test-HostAllowed $hostName) -and -not $IncludeBlocked) {
        $skipped += "$name ($hostName)"
        continue
    }
    $dir = Join-Path $root $cat
    if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
    $safe = ($name -replace '[\\/:*?"<>|]', '_')
    $path = Join-Path $dir "$safe.url"
    $text = "[InternetShortcut]`r`nURL=$url`r`n"
    [IO.File]::WriteAllText($path, $text, [Text.Encoding]::ASCII)
    $created++
}

Write-Host "Created $created shortcuts in '$root'" -ForegroundColor Green
if ($skipped.Count -gt 0) {
    Write-Host "Skipped (host not in the allow-list):" -ForegroundColor Yellow
    $skipped | ForEach-Object { Write-Host "  $_" }
    Write-Host "Add them with  setup-user-proxy.ps1 -Action Allow -Domains ...  and rerun, or use -IncludeBlocked."
}
Write-Host "`nNote: .url files open in the user's DEFAULT browser (he sets it himself in Settings > Apps > Default apps)."