Как запустить сценарий PowerShell при входе в систему для всех (текущих и будущих) пользователей с повышенными правами без UAC

Вопрос или проблема

Я разрабатываю скрипт входа для нашей компании. Некоторые из задач, которые он будет выполнять, включают установку принтеров, маппинг дисков, установку обоев и т.д. Многие из этих задач потребуют повышенных привилегий. Также для многих из этих задач потребуется имя пользователя (например, не может быть запущено от имени системы, необходимо наличие контекста пользователя для проверки групп и т.д.).

Сначала я посмотрел на Intune/MEM (мы на 100% используем M365/Azure, без локальных серверов). Вы можете перейти в AzureAD > Устройства > Скрипты и применить скрипт, но, насколько я понимаю, это выполняется только один раз, а затем снова только в случае изменения, а не при каждом входе, как хотелось бы.

Затем я попробовал shell:common startup. Я быстро осознал, что это приведет к появлению запроса UAC при каждом входе (внося изменения в реестр и т.д.). Это не сработает.

Потом я попробовал создать запланированное задание. Это хорошо работает, так как вы можете выполнить его от имени пользователя с переключателем “высшие привилегии”. Проблема, с которой я столкнулся, заключается в том, что вам нужно создать задачу для каждого пользователя. Мне действительно нужно, чтобы это применялось ко всем пользователям также как к будущим пользователям. Например, если новый пользователь входит в компьютер конференц-зала, мне нужно, чтобы скрипт выполнялся немедленно.

Вот в чем я сейчас нахожусь. Каковы мои варианты? Есть ли способ создать запланированное задание для всей системы, которое будет выполняться при входе каждого пользователя в контексте пользователя? Я не упускаю ли что-то в Microsoft Endpoint Management, что составляет правильный скрипт входа? Групповая политика? Я думаю, что папка startup полностью не подходит, но также открыт для других вариантов (хотя не хочу отключать UAC).

Существуют программные продукты, которые позволяют выполнять конкретные команды в режиме повышенных прав/администратора.

Примеры продуктов (может быть больше):

  • Steel Run As (коммерческий)
    Использует учетную запись администратора, скрывая требуемый пароль администратора, необходимый для его работы.

  • NirSoft AdvancedRun (бесплатный)
    Более продвинутая программа RunAs.

Еще одно решение может заключаться в использовании Планировщика задач для запланированного выполнения скрипта от имени администратора, но с триггером, который никогда не активируется, и запуском его с командой:

schtasks /run /tn "task-name"

Для получения дополнительной информации смотрите статью
Windows 7: Создание ярлыка программы с повышенными правами без запроса UAC.

Вот базовое решение, основанное на ответе @harrymc: (Отформатировано для удобства чтения)

schtasks.exe /Create 
  /SC ONLOGON 
  /RL HIGHEST 
  /ru "BUILTIN\Users" 
  /TN "companyname\Login Script" 
  /TR "'C:\Program Files\PowerShell\7\pwsh.exe' 
      -windowstyle hidden 
      -noninteractive 
      -ExecutionPolicy Bypass 
      -File C:\companyname\scripts\ls.launch.ps1" 
  /F

Вот полное решение, которое я реализовал, если кому-то интересно. Похоже, что оно работает хорошо. Мы используем политики Azure для большинства задач, но приятно иметь это запущенным на каждом компьютере на случай, если это будет нужно.

ls.install.bat

Устанавливает PowerShell, копирует необходимые скрипты и устанавливает запланированное задание.

@echo off
IF NOT EXIST c:\companyname mkdir c:\companyname
IF NOT EXIST c:\companyname\scripts mkdir c:\companyname\scripts
IF NOT EXIST c:\companyname\logs mkdir c:\companyname\logs
IF NOT EXIST p:\!scripts\ net use p: \\10.1.0.20\public /user:asdf asdf
IF EXIST p:\!scripts\ echo Диск уже замаплен
if NOT EXIST "C:\Program Files\PowerShell\7\pwsh.exe" (
    echo Установка PowerShell 7...
    start /wait msiexec.exe /i "p:\installs\PowerShell-7.1.3-win-x64.msi" /QN /L "c:\pwshinstall.log"
    echo Готово.
) else (
echo PowerShell уже установлен
)
echo Копирование файлов...
xcopy "p:\!scripts\companynameLoginScript\ls.launch.ps1" "c:\companyname\scripts" /Y
echo Готово. Установка задачи...
schtasks.exe /Create /SC ONLOGON /RL HIGHEST /ru "BUILTIN\Users" /TN "companyname\Login Script" /TR "'C:\Program Files\PowerShell\7\pwsh.exe' -windowstyle hidden -noninteractive -ExecutionPolicy Bypass -File C:\companyname\scripts\ls.launch.ps1" /F

ls.launch.ps1

Это то, что действительно запускается при старте. Он загружает обновленный скрипт и выполняет его, потому что было трудно сделать так, чтобы скрипт обновлял сам себя.

<#  
Скрипт: Лаунчер скрипта входа
Цель: Этот скрипт просто загружает скрипт входа и выполняет его.
Обновлено: 2021-10-08
#>

#----------------------------------------------#
#---- УСТРОЙСТВО/НАСТРОЙКА ПРИБОРОВ -----------#
#----------------------------------------------#

#$env:SEE_MASK_NOZONECHECKS = 1 #это для избежания предупреждений о безопасности при запуске инструментов, таких как baretail

$param1=$args[0]
if ($param1 -eq "-d" -or $param1 -eq "-D") {
    $isDev = $true
}

#todo: Переместить в файл конфигурации
$logpath = "c:\companyname\logs\loginscript"
$scriptpath = "c:\companyname\scripts\"
$scripturl = "http://servername/ls.core.ps1"
$logfile = "$(Get-Date -Format "yyyy-MM-dd HHmmss").log"


#----------------------------------------------#
#---- ФУНКЦИИ -------------------------------#
#----------------------------------------------#

Function log($message) {
    Write-Output "[$(Get-Date -Format "yyyy-MM-dd HHmmss")] $message" | Out-file "$($logpath)\$($logfile)" -append
    if ($isDev) { Write-Host "[$(Get-Date -Format "yyyy-MM-dd HHmmss")] $message" }
}

Function createFolder($path) {
    if (-!(Test-Path $path)) { New-Item -Type Directory -Path $path }
}

function updateScripts() {
    try {
        Invoke-WebRequest -uri $scripturl -OutFile "$($scriptpath)\ls.core.ps1"
    } catch {
        $statuscode = $_.Exception.Response.StatusCode
        Write-Host "StatusCode:" $statuscode
        if (Test-Path "$($scriptpath)\ls.core.ps1") {
            log "ОШИБКА: ($statuscode) НЕ УДАЛОСЬ ОБНОВИТЬ. Используется локальный скрипт."
        } else {
            log "ОШИБКА: Не удалось обновить скрипт и локальный скрипт не найден. Выход."
            exit
        }
    }
}

#----------------------------------------------#
#---- ОСНОВНОЙ БЛОК КОДА --------------------#
#----------------------------------------------#

if ($isDev) {
    log "Запуск в режиме разработки..."
}

createFolder $logpath
createFolder $scriptpath

#обновить скрипты
updateScripts
#выполнить основной скрипт входа
& "$($scriptpath)\ls.core.ps1"

ls.core.ps1

Это реальный скрипт входа. В настоящее время он просто устанавливает обои компании и меняет настройки в Microsoft Teams. Он также ведет логи в стандартное место, поэтому легко проверять наличие проблем. В конечном итоге он будет логироваться в систему агрегации логов.

<#
Скрипт: ls.core
Цель: основной скрипт входа для companyname
#>

#----------------------------------------------#
#---- УСТРОЙСТВО/НАСТРОЙКА ПРИБОРОВ -----------#
#----------------------------------------------#

$user = (Get-CimInstance CIM_ComputerSystem).Username
$wpfilename = "companyname_Wallpaper.jpg"
$wppath =  "c:\companyname\$wpfilename"
$wpurl = "http://downloads.companyname.com/$wpfilename"
$appdata = [Environment]::GetFolderPath([Environment+SpecialFolder]::ApplicationData)

#----------------------------------------------#
#---- ФУНКЦИИ -------------------------------#
#----------------------------------------------#

function isAdministrator
{
    $user = [Security.Principal.WindowsIdentity]::GetCurrent()
    (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}

Function log($message) {
    Write-Output "[$(Get-Date -Format "yyyy-MM-dd HHmmss")] $message" | Out-file "$($logpath)\$($logfile)" -append
    if ($isDev) { Write-Host "[$(Get-Date -Format "yyyy-MM-dd HHmmss")] $message" }
}

function downloadFile($source, $destination) {
    try {
        Invoke-WebRequest -uri $source -OutFile $destination
    } catch {
        $statuscode = $_.Exception.Response.StatusCode
        log "ОШИБКА: Не удалось загрузить $source ($statuscode)"
    }
}
function setLockScreenImage() {
    #TODO: Проверить, если экран блокировки уже установлен
    try {
        log "Установка экрана блокировки ($wppath)"
        REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PersonalizationCSP /f
        REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PersonalizationCSP /v LockScreenImagePath /t REG_SZ /d $wppath /f
        REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PersonalizationCSP /v LockScreenImageUrl /t REG_SZ /d $wppath /f
        REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PersonalizationCSP /v LockScreenImageStatus /t REG_DWORD /d 1 /f
    } catch {
        $statuscode = $_.Exception.Response.StatusCode
        log "ОШИБКА: Не удалось применить изменения реестра ($statuscode)"
    }
}

function setWallpaperImage() {
    #TODO: Проверить, если обои уже установлены
    try {
        log "Установка обоев ($wppath)"
        reg add "HKEY_CURRENT_USER\Control Panel\Desktop" /v Wallpaper /t REG_SZ /d $wppath /f
        rundll32.exe user32.dll, UpdatePerUserSystemParameters 1, True
    } catch {
        $statuscode = $_.Exception.Response.StatusCode
        log "ОШИБКА: Не удалось применить изменения реестра ($statuscode)"
    }
}

function updateTeams() {
    $configFile = "$appdata\Microsoft\Teams\desktop-config.json"
    if (Test-Path -Path $configFile) {

        #получить текущее значение
        $teamsConfig = (Get-Content $configFile -Raw) | ConvertFrom-Json
        $isOpenAsHidden = $teamsConfig.appPreferenceSettings.psobject.properties.Where({$_.name -eq "openAsHidden"}).value

        #обновить файл только если он еще не установлен
        if (!$isOpenAsHidden) {
            log "Обновление $configFile"
            Stop-Process -name "Teams"
            try {
                $currSettings = Get-Content $configFile
                $currSettings.replace('"openAsHidden":false', '"openAsHidden":true') | Set-Content $configFile
            } catch {
                $statuscode = $_.Exception.Response.StatusCode
                if (!$statuscode) { $statuscode = "UNKNOWN CODE"}
                log "ОШИБКА: Не удалось обновить настройки Teams ($statuscode)"
            }
            & $appdata\Microsoft\Teams\Update.exe --processStart "Teams.exe"
        } else {
            log "Teams уже настроен на скрытый запуск"
        }
    } else {
        log "ОШИБКА: Файл конфигурации Teams не найден ($configFile)"
    }
}

#----------------------------------------------#
#---- ОСНОВНОЙ БЛОК КОДА --------------------#
#----------------------------------------------#

log "$user вошел в систему"

if(-!(Test-Path $wppath)) {
    downloadFile $wpurl $wppath
}
if(Test-Path $wppath) {
    setLockScreenImage
    SetWallpaperImage
}

updateTeams

log "Скрипт входа завершен"

Ответ или решение

Для запуска скрипта PowerShell при входе в систему для всех текущих и будущих пользователей с повышенными привилегиями без запроса UAC, вам подойдет использование планировщика задач Windows. Этот подход гарантирует, что скрипт будет выполняться в контексте пользователя и с необходимыми правами, не запрашивая подтверждение на каждом входе.

Шаги для реализации:

  1. Создайте основной скрипт логина. Например, мы создаем PowerShell скрипт ls.launch.ps1, который будет выполнять ваши требования (например, установка драйверов принтеров, подключение сетевых дисков и т.д.).

  2. Создайте .bat файл для установки задач в планировщике. Данный файл будет содержать команды для создания задачи в планировщике, которая будет запускать ваш PowerShell скрипт при каждом входе пользователя.

  3. Команда для создания задачи. Используйте следующую команду schtasks.exe для создания задачи:

schtasks.exe /Create /SC ONLOGON /RL HIGHEST /RU "BUILTIN\Users" /TN "companyname\Login Script" /TR "'C:\Program Files\PowerShell\7\pwsh.exe' -windowstyle hidden -noninteractive -ExecutionPolicy Bypass -File C:\companyname\scripts\ls.launch.ps1" /F

Пример .bat Скрипта:

@echo off
IF NOT EXIST c:\companyname mkdir c:\companyname
IF NOT EXIST c:\companyname\scripts mkdir c:\companyname\scripts
IF NOT EXIST c:\companyname\logs mkdir c:\companyname\logs

echo Copying files...
xcopy "p:\!scripts\companynameLoginScript\ls.launch.ps1" "c:\companyname\scripts" /Y

echo Installing task...
schtasks.exe /Create /SC ONLOGON /RL HIGHEST /RU "BUILTIN\Users" /TN "companyname\Login Script" /TR "'C:\Program Files\PowerShell\7\pwsh.exe' -windowstyle hidden -noninteractive -ExecutionPolicy Bypass -File C:\companyname\scripts\ls.launch.ps1" /F

Содержимое ls.launch.ps1:

# Ваша логика выполнения, например:
& "C:\companyname\scripts\ls.core.ps1"

Преимущества использования этого подхода:

  • Автоматизация: Скрипт будет выполняться при каждом входе пользователя без необходимости ручной настройки для новых пользователей.

  • Гибкость: Вы можете обновлять внутренности ls.launch.ps1 без необходимости изменения настроек планировщика задач.

  • Управляемость: Все действия логируются, и вы можете видеть, что выполнился весь процесс.

Заключение

Данный подход позволяет эффективно и без дополнительных накладных расходов создавать централизованные логин-скрипты для вашей компании. Убедитесь, что тестируете этот скрипт на пилотной группе пользователей перед его широкомасштабным развертыванием, чтобы гарантировать, что все выполняется ожидаемо и без ошибок. Если в будущем потребуется изменить или расширить функционал, всегда можно легко модифицировать созданные PowerShell скрипты.

Эта методология также может быть дополнительно адаптирована под требования вашей корпоративной инфраструктуры, что делает ее универсальным решением для управления пользовательскими настройками и автоматизацией различных задач.

Оцените материал
Добавить комментарий

Капча загружается...