Рубрика: Скрипты

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

  • Как автоматизировать Cisco AnyConnect и забыть про ручное переключение VPN

    Проблема: рутина при смене сетей

    Когда постоянно мотаешься между отделами или часто работаешь из дома, ручное переключение VPN быстро начинает бесить. Ты берешь ноутбук, переходишь в другую зону, цепляешься к новому Wi-Fi — и туннель, естественно, отваливается. Приходится каждый раз лезть в клиент и переподключаться руками.

    Хотелось получить простое решение: чтобы ноут сам понимал, где он сейчас находится — внутри доверенной корпоративной сети (где VPN не нужен) или снаружи, и автоматически поднимал туннель без моего участия.

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

    Правильнее ловить ивенты самой операционной системы. Когда Windows успешно подключается к любой сети (неважно, Wi-Fi это или патч-корд), служба NetworkProfile генерирует в системном журнале событие с ID 10000. Мы просто вешаем запуск нашего PowerShell-скрипта на этот триггер через Task Scheduler. В итоге скрипт спит и «дергается» только в момент реальной смены сети.

    Как общаться с vpncli.exe У AnyConnect есть консольная утилита vpncli.exe. Сложность в том, что она интерактивная. При попытке подключения она начинает сыпать вопросами в консоль: просит подтвердить недоверенный сертификат, выбрать группу доступа, ввести логин и пароль.

    Чтобы автоматизировать это, мы формируем в скрипте одну строку со всеми ответами по порядку, разделяя их переносом строки (символ `n). Затем просто скармливаем эту матрицу ответов утилите через конвейер, используя ключ -s для чтения стандартного ввода.

    Безопасность: как не засветить пароль Хранить пароль от VPN в открытом текстовом файле или внутри самого скрипта нельзя. Заюзаем встроенный механизм Windows — DPAPI.

    Мы один раз вводим пароль руками, скрипт превращает его в SecureString и сохраняет в файл. Фишка в том, что винда шифрует эту строку ключом вашей текущей учетной записи. Этот файл физически невозможно расшифровать, если скопировать его на другой комп или попытаться прочитать из-под другого пользователя. Основной скрипт будет на лету читать этот файл, расшифровывать пароль в оперативную память на долю секунды, передавать в AnyConnect и тут же затирать переменные. Само собой это тоже не супер защищенный момент но щито поделать-десу.

    Пошаговая настройка и финальный скрипт

    Шаг 1. Создаем файл с зашифрованным паролем Откройте PowerShell (с теми правами, под которыми вы работаете) и выполните эту команду. Она запросит пароль и сложит зашифрованный хеш в текстовик:

    Тык
    $cred = Read-Host -Prompt "Введите пароль от VPN" -AsSecureString
    $cred | ConvertFrom-SecureString | Set-Content -Path "C:\Scripts\vpn_pass.txt" -Force
    

    Шаг 2. Основной скрипт Создаём файл C:\Scripts\AutoVPN.ps1 и контрол-вэшним в него этот код. Главное не забыть вписать свои названия банковских сетей в массив $bankNetworks что бы исключить их их этого цирка.

    Тык
    
    # Массив имен доверенных сетей (SSID или домены)
    $bankNetworks = @("Bank_WiFi_1", "Bank_WiFi_2", "Corp_LAN_Domain") 
    
    $vpncli = "C:\Program Files (x86)\Cisco\Cisco AnyConnect Secure Mobility Client\vpncli.exe"
    $vpnProfile = "11.22.33.44:1234"
    $username = "username"
    $encryptedPassPath = "C:\Scripts\vpn_pass.txt"
    
    $currentNetworks = Get-NetConnectionProfile | Select-Object -ExpandProperty Name
    
    $isBankNetwork = $false
    foreach ($net in $currentNetworks) {
        if ($bankNetworks -contains $net) {
            $isBankNetwork = $true
            break
        }
    }
    
    # Если активно подключение к одной из сетей банка - прерываем выполнение
    if ($isBankNetwork) {
        Exit
    }
    
    
    $vpnState = & $vpncli state
    
    # Если туннель уже поднят - прерываем выполнение (защита от зацикливания)
    if ($vpnState -match "state: Connected") {
        Exit
    }
    
    # Читаем файл как единую строку и отсекаем возможные невидимые символы
    $encryptedString = (Get-Content $encryptedPassPath -Raw).Trim()
    
    $secureString = ConvertTo-SecureString $encryptedString
    $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString)
    $plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
    [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) # Очистка памяти BSTR
    
    $vpnInput = "y`n1`n$username`n$plainPassword"
    
    # Передача матрицы в стандартный ввод vpncli
    $vpnInput | & $vpncli -s connect $vpnProfile
    
    $plainPassword = $null
    $vpnInput = $null
    

    Шаг 3. Настройка Планировщика задач (Task Scheduler)

    Ну, тут все по классике, единственное что посоветую, делать delay на секунд 15 на случай если получение IP от маршрутизатора затянется.

    В целом полезно. Спасибо за внимание!

  • “Безопасная” смена пароля пользователя AD.

    Приветствую. После того как я полностью автоматизировал AD в организации, появилась острая необходимость в безопасной передаче пароля конечному пользователю. По регламенту организации сотрудники тех поддержки не должны видеть пароль но при этом должны как то его сбросить и передать. Решение было быстрым и тривиальным. Написал простенький инструмент. конвертировал его в EXE и отдал сотрудникам. Сразу же сообщу. В коде много обращений к API внутренних сервисов организации, у вас же это может сильно отличатся. например функции отправки СМС и получения инфы о пользователе завязаны на веб-серверах, у вас это может иметь другую реализацию. На худой конец, вы всегда можете закомментировать не нужные строки и пользоваться кор-функционалом с отправкой данных только на e-mail.

    Тыкни
    <#
    Итак. Все обращения к базам данных HR у нас осуществляются по API. У вас же это может выполнятся разными способами. 
    #>
    $global:headers = @{
        "content-type"  = "application/json"
        "Authorization" = "Basic SOMEA123123123123123TOKEN"
    }
    # Запрос к API базы HR  
    function Invoke-APIRequest 
    {
        param (
            [Parameter(Mandatory)]
            [string]$Uri,
            [Parameter()]
            [string]$Method = 'POST',
            [Parameter()]
            [string]$Body
        )
        return Invoke-RestMethod -Uri $Uri -Method $Method -Headers $global:headers -Body $Body
    }
    # Получение списка сотрудников
    function Get-Employees 
    {
    $body = @"
    {
            "get_child_departments":"True"
    }
    "@
    return $(Invoke-APIRequest -Uri 'https://hr.domain.site/api/employees' -Body $body)
    }
    # Получение всех Департаментов банка
    function Get-Departments 
    {
    $body = @"
    {
            "get_child_departments":"True"
    }
    "@
        return Invoke-APIRequest -Uri 'https://hr.domain.site/api/gate/department' -Body $body
    }
    # Очевидно
    function get-userinf
    {
    $ipregex = "^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"
    [string]$date = get-date -Format "yyyy.MM.dd hh-mm"
    $ipaddress = Get-NetIPAddress | ? IPAddress -Match  $ipregex | select IPAddress
    $return = @{
    "Username" = $($env:USERNAME)
    "ComputerName" = $($env:COMPUTERNAME)
    "IPAdress" = $ipaddress.IPAddress
    "ExecutionDate" = $($date)
    }
    
    return ConvertTo-Json $return
    }
    # Очевидно
    function Check-User 
    {
        param (
            [Parameter(Mandatory)]
            [string]$samaccountname = "ttestesteronyan"
        )
    
        try {
            # получаем пользователя
            $user = Get-ADUser -Filter * -Properties GivenName,Surname,Name,Department,Title,Mobile,wWWHomePage,EmployeeID,EmployeeNumber,whenCreated |`
                  ? {if(($_.EmployeeID -eq $samaccountname) -or ($_.Name -eq $samaccountname) -or ($_.employeeNumber -eq $samaccountname) -or ($_.SamaccountName -eq $samaccountname)){$_}}
            $HRuser = (get-Employees).data `
                    | ? {if(($_.employee_id -eq $samaccountname) -or ($_.Name -eq $samaccountname) -or ($_.system_id -eq $samaccountname) -or ($_.name -eq $user.name)){$_}} `
                    | select firstname, lastname, Name, department_id, Employee_ID, email, phone_number ,System_ID 
    
            # собираем данные
            $output = @()
            if($user){
            $output += "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *"
            $output += "Информация о пользователе $Username :"
            $output += "====================================="
            $output += "Логин: $($user.samaccountname)"
            $output += "Статус учетки пользователя: $(if($user.enabled){"включена"}else{"выключена"})"
            $output += "Имя: $($user.GivenName)"
            $output += "Фамилия: $($user.Surname)"
            $output += "ФИО: $($user.Name)"
            $output += "Департамент: $($user.Department)"
            $output += "Должность: $($user.Title)"
            $output += "Мобильный телефон: $($user.Mobile)"
            $output += "Личная почта: $($user.wWWHomePage)"
            $output += "Номер сотрудника: $($user.EmployeeID)"
            $output += "ID сотрудника: $($user.EmployeeNumber)"
            $output += "Дата создания: $($user.whenCreated)"
            $output += "====================================="
            }
            else{
            $output = @()
            }
            if($HRuser){
            $output += "Информация о пользователе $samaccountname в базе HR:"
            $output += "====================================="
            $output += "Имя: $($HRuser.firstname)"
            $output += "Фамилия: $($HRuser.lastname)"
            $output += "ФИО: $($HRuser.Name)"
            $output += "Департамент: $(((Get-Departments).data  | ? id -eq $HRuser.department_id).name)"
            $output += "Должность: $($HRuser.post)"
            $output += "Мобильный телефон: $($HRuser.phone_number)"
            $output += "Личная почта: $($HRuser.email)"
            $output += "Номер сотрудника: $($HRuser.Employee_ID)"
            $output += "ID сотрудника: $($HRuser.System_ID)"
            $output += "====================================="
            }
    
            if(!$user -and !$HRuser){
            return "ПОльзователь не найден ни в АД ни в базе кадров."
            }
            elseif($user -and !$HRuser){
            $output +=  "Пользователь не найден в базе кадров но есть в AD."
            return ($output -join "`r`n")        
            }
            elseif(!$user -and $HRuser){
            $output +=  "Пользователь не найден в AD но найден в базе кадров."
            return ($output -join "`r`n")        
            }
            else{
            return ($output -join "`r`n")        
            }
        }
        catch {
            return "Ошибка при получении информации о пользователе $Username : $_"
        }
    }
    # Очевидно
    function Write-Log 
    {
        param (
            [Parameter(ValueFromPipeline = $true)]
            [string]$Message,
            [Parameter(Mandatory = $true)]
            [string]$LogFilePath
        )
        process {
            $timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
            $logEntry = "$($timestamp): $Message"
            Add-Content -Path $LogFilePath -Value $logEntry
        }
    }
    # Генерация пароля
    function Get-RandomPassword 
    {
        Param (
            [Parameter(Mandatory)]
            [int] $length
        )
        
        $CharSet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.ToCharArray()
        $SpecialCharSet = '!@'.ToCharArray()
        $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
        $bytes = New-Object byte[]($length)
        $rng.GetBytes($bytes)
        $result = New-Object char[]($length)
        
        # Генерация пароля без спецсимволов
        for ($i = 0 ; $i -lt $length ; $i++) {
            $result[$i] = $CharSet[$bytes[$i] % $CharSet.Length]
        }
        
        # Вставка специального символа в случайную позицию
        $randomIndex = Get-Random -Minimum 0 -Maximum $length
        $specialChar = $SpecialCharSet[$(Get-Random -Minimum 0 -Maximum $SpecialCharSet.Length)]
        $result[$randomIndex] = $specialChar
        
        return -join $result
    }
    # отправка смс по номеру телефона и мыло туда же
    function ConvertTo-Lat 
    {
        param ([string]$russian)
        $alphabet = @{
            "а" = "a"; "б" = "b"; "в" = "v"; "г" = "g"; "д" = "d";
            "е" = "e"; "ё" = "yo"; "ж" = "j"; "з" = "z"; "и" = "i";
            "й" = "i"; "к" = "k"; "л" = "l"; "м" = "m"; "н" = "n";
            "о" = "o"; "п" = "p"; "р" = "r"; "с" = "s"; "т" = "t";
            "у" = "u"; "ф" = "f"; "х" = "h"; "ц" = "c"; "ч" = "ch";
            "ш" = "sh"; "щ" = "sch"; "ь" = ""; "ъ" = ""; "ы" = "y";
            "э" = "e"; "ю" = "yu"; "я" = "ya"; " " = " ";
        }
        $russian_chars = $russian.ToCharArray()
        $russian_in_lat = ""
        foreach ($char in $russian_chars) {
            $stringChar = $char.ToString()
            $lowerChar = $stringChar.ToLower()
            if ($alphabet.ContainsKey($lowerChar)) {
                $translatedChar = $alphabet[$lowerChar]
                if ($stringChar -ne $lowerChar) {
                    $translatedChar = $translatedChar[0].ToUpper() + $translatedChar.Substring(1)
                }
                $russian_in_lat += $translatedChar
            } else {
                $russian_in_lat += $char
            }
        }
        return $russian_in_lat
    }
    # получение токена авторизации для смс-шлюза
    function Get-Token 
    {
    try{
    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("Content-Type", "application/json")
    $body = @"
    {
        `"username`": `"someUser`",
        `"password`": `"PrettyStronGPa$$w0rd`"
    }
    "@
    $response = Invoke-RestMethod 'https://smsgtw.domain.site/api/auth/token' -Method 'POST' -Headers $headers -Body $body
    return $response
    
    }
    catch{
    return $Error[0].Exception.Message
    }
    }
    # отправка смс по номеру телефона
    function Send-SMS 
    {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
    $AuthToken,
    $phone_number,
    $purpose,
    $message
    )
    
    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("Content-Type", "application/json")
    $headers.Add("Authorization", "Bearer $AuthToken")
    
    $body = @"
    {
        `"purpose`": `"$purpose`",
        `"phone_number`": `"$phone_number`",
        `"message`": `"$message`",
    }
    "@
    
    $response = Invoke-RestMethod 'https://smsgtw.domain.site/api/messages' -Method 'POST' -Headers $headers -Body $body 
    $response.data.when_sent | ConvertTo-Json
    
    }
    # отправка смс по номеру телефона и мыло туда же
    function Send-Notification 
    {
        [CmdletBinding(SupportsShouldProcess = $true)]
        param(
            [string]   $Email,
            [string]   $PhoneNum,
            [string]   $EmailBody,
            [string]   $SmsBody
        )
        $p = ConvertTo-SecureString -String "PrettyStronGPa$$w0rd" -AsPlainText -Force 
        $u = "robot@domain.site"
        $smtpCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $u, $p
    
        $hasEmail = -not [string]::IsNullOrWhiteSpace($Email)
        $hasPhone = -not [string]::IsNullOrWhiteSpace($PhoneNum)
    
        if (-not $hasEmail -and -not $hasPhone) {
            return "Ошибка: не указан ни адрес электронной почты, ни номер телефона."
        }
        $combinedBody = $EmailBody, $SmsBody -join " | "
    
        $sentEmail = $false
        $sentSms   = $false
    
        try {
            if ($hasEmail -and $hasPhone) {
                Send-MailMessage -To $Email `
                                 -From $u `
                                 -Subject "Уведомление" `
                                 -Body $EmailBody `
                                 -SmtpServer "smtp.domain.site" `
                                 -Credential $smtpCredential `
                                 -UseSsl `
                                 -Encoding UTF8
    
                $sentEmail = $true
                Send-SMS         -AuthToken (Get-Token).access_token `
                                 -phone_number $PhoneNum `
                                 -purpose "Send User's data" `
                                 -message "$SmsBody"
                $sentSms = $true
            }
            elseif ($hasEmail) {
                Send-MailMessage -To $Email `
                                 -From $u `
                                 -Subject "Уведомление" `
                                 -Body $combinedBody `
                                 -SmtpServer "smtp.domain.site" `
                                 -Credential $smtpCredential `
                                 -UseSsl `
                                 -Encoding UTF8
                $sentEmail = $true
            }
            else {
                Send-SMS         -AuthToken (Get-Token).access_token `
                                 -phone_number $PhoneNum `
                                 -purpose "Send User's data" `
                                 -message "$((ConvertTo-Lat $combinedBody).replace("_"," "))"
                $sentSms = $true
            }
    
            return [pscustomobject]@{
                DateTime  = Get-Date
                EmailSent = $sentEmail
                SmsSent   = $sentSms
            }
        }
        catch {
            return $_.Exception
        }
    }
    # Смена пароля пльзователя
    function Reset-ADUserPassword
    {
    param($samaccountname)
    try{
        $chekADuser = Get-ADUser -Filter * -Properties GivenName,Surname,Name,Department,Title,MobilePhone,HomePage,EmployeeID,EmployeeNumber,whenCreated |`
                    ? {if(($_.EmployeeID -eq $samaccountname) -or ($_.Name -eq $samaccountname) -or ($_.employeeNumber -eq $samaccountname) -or ($_.SamaccountName -eq $samaccountname)){$_}}
            if(!$chekADuser){
                $result = "$(get-date): Не найден пользователь"
            }
            else{
                if(![string]::IsNullOrWhiteSpace($chekADuser.HomePage) -and ![string]::IsNullOrWhiteSpace($chekADuser.MobilePhone)){
                 $passsword = Get-RandomPassword -length 10
                     try{
                         Set-ADAccountPassword -Identity $chekADuser.DistinguishedName  `
                                               -NewPassword (ConvertTo-SecureString -String $passsword -AsPlainText -Force) `
                                               -Confirm:$false 
                         Set-ADUser $chekADuser.DistinguishedName -ChangePasswordAtLogon $true -Confirm:$false
                         Send-Notification -Email $chekADuser.HomePage `
                                           -PhoneNum $chekADuser.MobilePhone.Replace('+',"") `
                                           -EmailBody "Пароль пользователя $($chekADuser.samaccountname) был сброшен и отправлен на мобильный телефон."`
                                           -SmsBody "One time password: $passsword" 
                     $result = "$(get-date): Cмена пароля у пользователя $($chekADuser.samaccountname) прошла успешно, пароль отправлен и на E-mail и на мобильный телефон."
                        }
                     catch{
                         $result = "$(get-date): Произошла ошибка при сменен пароля у пользователя $($chekADuser.samaccountname): $($Error[0].exception.message)"
                     }
                }
                elseif(![string]::IsNullOrWhiteSpace($chekADuser.HomePage) -and [string]::IsNullOrWhiteSpace($chekADuser.MobilePhone)){
                # Это если нет телефона
                $passsword = Get-RandomPassword -length 10
                     try{
                         Set-ADAccountPassword -Identity $chekADuser.DistinguishedName  `
                                               -NewPassword (ConvertTo-SecureString -String $passsword -AsPlainText -Force) `
                                               -Confirm:$false 
                         Set-ADUser $chekADuser.DistinguishedName -ChangePasswordAtLogon $true -Confirm:$false
                         Send-Notification -Email $chekADuser.HomePage `
                                           -PhoneNum $chekADuser.MobilePhone `
                                           -EmailBody "Пароль пользователя $($chekADuser.samaccountname) был сброшен." `
                                           -SmsBody "One time password: $passsword"
                         $result = "$(get-date): Cмена пароля у пользователя $($chekADuser.samaccountname) прошла успешно, пароль отправлен на E-mail, МОБИЛЬНЫЙ НЕ УКАЗАН!!!."
                        }
                     catch{
                         $result = "$(get-date): Произошла ошибка при сменен пароля у пользователя $($chekADuser.samaccountname): $($Error[0].exception.message)"
                     }
                }
                elseif([string]::IsNullOrWhiteSpace($chekADuser.HomePage) -and ![string]::IsNullOrWhiteSpace($chekADuser.MobilePhone)){
                # Это если нет Email
                $passsword = Get-RandomPassword -length 10
                     try{
                         Set-ADAccountPassword -Identity $chekADuser.DistinguishedName  `
                                               -NewPassword (ConvertTo-SecureString -String $passsword -AsPlainText -Force) `
                                               -Confirm:$false 
                         Set-ADUser $chekADuser.DistinguishedName -ChangePasswordAtLogon $true -Confirm:$false
                         Send-Notification -Email $chekADuser.HomePage `
                                           -PhoneNum $chekADuser.MobilePhone.Replace('+',"") `
                                           -EmailBody "Hi!"`
                                           -SmsBody "$($chekADuser.samaccountname) | one time password: $passsword" 
                        $result = "$(get-date): Cмена пароля у пользователя $($chekADuser.samaccountname) прошла успешно, пароль отправлен на мобильный телефон, ЭЛЕКТРОННАЯ ПОЧТА НЕ УКАЗАНА!!!."
                        }
                     catch{
                        $result = "$(get-date): Произошла ошибка при сменен пароля у пользователя $($chekADuser.samaccountname): $($Error[0].exception.message)"
                     }
                    
                }
                elseif([string]::IsNullOrWhiteSpace($chekADuser.HomePage) -and [string]::IsNullOrWhiteSpace($chekADuser.MobilePhone)){
                # Это если нет Email и телефона
                      $result = "$(get-date): Cмена пароля у пользователя $($chekADuser.samaccountname) отменена, мобильный телефон и электронная почта не указаны в AD и в базе HR!!!."  
                }
                else{
                # Любая другая херня
                    $result = "$(get-date): Произошла ошибка при сменен пароля у пользователя необходимо уточнение данных по этому пользователю."
                }
            }
            }
        catch [System.Management.Automation.CommandNotFoundException]{
        $result = "$(get-date): У вас не установлены средства управления AD! Вы точно привелигированный пользователь?? На всякий случай я отправил ваши данные в ДИБ!!!"
        $whois = get-userinf
        Send-Notification -Email alert@domain.site `
                                      -PhoneNum $null `
                                      -EmailBody "Обнаружена попытка сменить пароль пользователя $samaccountname на компьютере с отсутвующими инструментами администрирования AD!!! Возьмите 'на карандаш'!" `
                                      -SmsBody $whois
        }
    return ($result  + "`r`n")
    }
    
    #
    
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing
    # Форма
    $form = New-Object System.Windows.Forms.Form
    $form.Text = "Сброс пароля пользователя"
    $form.Size = New-Object System.Drawing.Size(700,400)
    $form.StartPosition = "CenterScreen"
    $form.FormBorderStyle = 'FixedDialog'
    $form.MaximizeBox = $false
    
    # Метка
    $label = New-Object System.Windows.Forms.Label
    $label.Text = "Введите логин, или ФИО, или ID пользователя"
    $label.Location = New-Object System.Drawing.Point(20,20)
    $label.AutoSize = $true
    $form.Controls.Add($label)
    
    # Поле ввода
    $textBox = New-Object System.Windows.Forms.TextBox
    $textBox.Location = New-Object System.Drawing.Point(20,50)
    $textBox.Size = New-Object System.Drawing.Size(300,20)
    $form.Controls.Add($textBox)
    
    # Кнопка «Сменить пароль!»
    $resetButton = New-Object System.Windows.Forms.Button
    $resetButton.Text = "Сменить пароль!"
    $resetButton.Location = New-Object System.Drawing.Point(510,45)
    $resetButton.Size = New-Object System.Drawing.Size(120,30)
    $form.Controls.Add($resetButton)
    
    # Кнопка «Проверить пользователя»
    $checkButton = New-Object System.Windows.Forms.Button
    $checkButton.Text = "Проверить пользователя"
    $checkButton.Location = New-Object System.Drawing.Point(340,45)
    $checkButton.Size = New-Object System.Drawing.Size(160,30)
    $form.Controls.Add($checkButton)
    
    # Текстовое поле для логов
    $logBox = New-Object System.Windows.Forms.TextBox
    $logBox.Location = New-Object System.Drawing.Point(20,100)
    $logBox.Size = New-Object System.Drawing.Size(640,220)
    $logBox.Multiline = $true
    $logBox.ScrollBars = "Vertical"
    $logBox.ReadOnly = $true
    $form.Controls.Add($logBox)
    
    # Прогрессбар ниже logBox, не налезает
    $progressBar = New-Object System.Windows.Forms.ProgressBar
    $progressBar.Location = New-Object System.Drawing.Point(20,340)  # чуть ниже logBox
    $progressBar.Size = New-Object System.Drawing.Size(640,20)       # по ширине logBox
    $progressBar.Style = 'Marquee'
    $progressBar.Visible = $false
    $form.Controls.Add($progressBar)
    
    # Действие кнопки
    $resetButton.Add_Click({
        $username = $textBox.Text
        if ([string]::IsNullOrWhiteSpace($username)) {
            $logBox.AppendText("$(get-date):* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *`r`n")
            $logBox.AppendText("$(get-date):Введите логин пользователя!`r`n")
            return
        }
    
        $logBox.AppendText("$(get-date): Сброс пароля для пользователя $username...`r`n")
        try {
            # Вызов твоей функции
            $logBox.AppendText("$(get-date):* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *`r`n")
            $result = Reset-ADUserPassword -samaccountname $username
            $logBox.AppendText($result + "`r`n")
        }
        catch {
            $logBox.AppendText("$(get-date):* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *`r`n")
            $logBox.AppendText("$(get-date): Ошибка: $_`r`n")
        }
    })
    
    # Действие кнопки проверки пользователя
    $checkButton.Add_Click({
        $progressBar.Visible = $true
        $form.Refresh()
        $username = $textBox.Text
        if ([string]::IsNullOrWhiteSpace($username)) {
            $logBox.AppendText("$(get-date):* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *`r`n")
            $logBox.AppendText("$(get-date): Введите логин пользователя!`r`n")
            $progressBar.Visible = $false
            return
        }
        $logBox.AppendText("$(get-date): Проверка пользователя $username...`r`n")
        try {
            $result = Check-User -samaccountname $username
            $logBox.AppendText("$(get-date):* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *`r`n")
            $logBox.AppendText($result + "`r`n")
        }
        catch {
            $logBox.AppendText("$(get-date):* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *`r`n")
            $logBox.AppendText("Ошибка: $_`r`n")
        }
        $progressBar.Visible = $false
    })
    
    # Показ формы
    $form.Add_Shown({$form.Activate()})
    [void]$form.ShowDialog()
    
    

  • Как же бесят Datastore ISO в дисководе.

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

    Тыкни
    Connect-VIServer -Server vcenter.site -Credential (Get-Credential administrator@vsphere.local)
    
    $servers = get-vm * | select Folder, Name, VMHost
    #Машины обрабатываются по папке в VCenter но это можно удалить и пройтись по всем машинам в VCenter если закомментить оператора IF
    $foldername = "LinuxInfra"
    
    Foreach($serv in $servers){
    if($serv.folder.name -eq $foldername){
    $vm = Get-VM "$($serv.Name)"
    $cd = Get-CDDrive -VM $vm
    $spec = New-Object VMware.Vim.VirtualMachineConfigSpec
    $cdSpec = New-Object VMware.Vim.VirtualDeviceConfigSpec
    $cdSpec.Operation = [VMware.Vim.VirtualDeviceConfigSpecOperation]::Edit
    $cdSpec.Device = $cd.ExtensionData
    $cdSpec.Device.Backing = New-Object VMware.Vim.VirtualCdromRemoteAtapiBackingInfo
    $cdSpec.Device.Backing.DeviceName = $null
    $cdSpec.Device.Connectable.Connected = $false
    $cdSpec.Device.Connectable.StartConnected = $false
    $cdSpec.Device.Connectable.AllowGuestControl = $true
    $spec.DeviceChange += $cdSpec
    $task = $vm.ExtensionData.ReconfigVM_Task($spec)
    Write-Host "Ожидание завершения операции..."
    while ($task.Info.State -eq "running" -or $task.Info.State -eq "queued") {
        Start-Sleep -Seconds 1
        $task = Get-View -Id $task.MoRef
    }
    Write-Host "ISO диск отключен от $($serv.Name)"
    }
    } 
    
  • Создание задач по репликации Veeam B&R с помощью Powershell

    Мне довелось создать реплику виртуальных машин средствами Veeam B&R. Забавно то что используя GUI это делается за пару кликов, однако задача состояла в том что бы создать реплики на более чем 15 машин при этом каждая машина должна быть отдельной джобой. Что бы не терять большое количество времени, я решил написать небольшой скрипт на Powershell который сделает это за меня. Я постарался максимально унифицировать этот скриптик, но думаю можно будет сделать это еще эффективнее. Тело скрипта под катом.

    Тыкни
    function New-ReplicaJob {
        param (
            [string]$JobName, 
            [string]$TargetServerIP, 
            [string]$VMName, 
            [string]$TargetDatastore,
            [string]$SourceIP, 
            [string]$SourceMask, 
            [string]$ReIpTargetIP, 
            [string]$ReIpTargetMask,
            [string]$ReIpGateway, 
            [string]$ReIpDNS, 
            [string]$SourceNetwork, 
            [string]$TargetNetwork,
            [string]$SourceProxy, 
            [string]$TargetProxy, 
            [string]$Repository = "DefaultRepo"
        )
    
        $Description = "$JobName replication job"
        $RestorePointsToKeep = 7
    
        $TargetServer = Get-VBRServer -Name    $TargetServerIP
        $VMObject = Find-VBRViEntity -Name $VMName
        $Datastore = Find-VBRViDatastore -Name $TargetDatastore -Server $TargetServerIP
        $ReIpRule = New-VBRViReplicaReIpRule -SourceIp $SourceIP -SourceMask $SourceMask -TargetIp $ReIpTargetIP -TargetMask $ReIpTargetMask -TargetGateway $ReIpGateway -DNS $ReIpDNS
    
        $SrcNetwork = Get-VBRViServerNetworkInfo -Server $VMObject.VmHostName | Where-Object { $_.Name -eq $SourceNetwork }
        $TgtNetwork = Get-VBRViServerNetworkInfo -Server $TargetServer.Name | Where-Object { $_.Name -eq $TargetNetwork }
    
        $SrcProxy = Get-VBRViProxy -Name $SourceProxy
        $TgtProxy = Get-VBRViProxy -Name $TargetProxy
    
        Add-VBRViReplicaJob -Name $JobName `
                            -Server $TargetServer `
                            -Entity $VMObject `
                            -Datastore $Datastore `
                            -Suffix "_replica" `
                            -BackupRepository $Repository `
                            -Description $Description `
                            -EnableNetworkMapping `
                            -SourceNetwork $SrcNetwork `
                            -TargetNetwork $TgtNetwork `
                            -SourceProxy $SrcProxy `
                            -TargetProxy $TgtProxy `
                            -RestorePointsToKeep $RestorePointsToKeep `
                            -ReIpRule $ReIpRule `
                            -HighPriority `
                            -Force
    }
    
    Connect-VIServer "$(Read-Host 'Enter vCenter hostname or IP')" -Credential (Get-Credential)
    Connect-VBRServer -Server "$(Read-Host 'Enter Veeam hostname or IP')" -Credential (Get-Credential)
    
    $VMList = @(
    "$(Read-Host 'Enter VM names to replicate, separated by comma (no spaces)')".Split(",") | Where-Object { $_ -ne '' }
    )
    
    foreach ($VM in $VMList) {
        $GuestIP = (Get-VM $VM).Guest.IPAddress[0]
        $GuestNet = (Get-VM $VM).Guest.Nics.NetworkName[0]
        $LastOctet = $GuestIP.Split(".")[-1]
        $NewTargetIP = "192.168.100.$LastOctet"
        $NewGateway = "192.168.100.1"
    
        New-ReplicaJob -JobName $VM `
                       -TargetServerIP "192.168.0.10" `
                       -VMName $VM `
                       -TargetDatastore "TargetDatastore" `
                       -SourceIP $GuestIP `
                       -SourceMask "255.255.255.0" `
                       -ReIpTargetIP $NewTargetIP `
                       -ReIpTargetMask "255.255.255.0" `
                       -ReIpGateway $NewGateway `
                       -ReIpDNS "192.168.100.10" `
                       -SourceNetwork $GuestNet `
                       -TargetNetwork "TargetNetworkName" `
                       -SourceProxy "SourceProxyName" `
                       -TargetProxy "TargetProxyName" `
                       -Repository "DefaultRepo"
    }
    
  • Проблемы с IE?

    Недавно я столкнулся с острой необходимостью прямой работы с IE на Windows 11. Однако, я столкнулся с тем что просто режим совместимости в EDGE совершенно недостаточно. Немного поразмышляв, я пришел к выводу что ничто так не симулирует стабильную работу IE как сам IE. На просторах Интернета я нашел довольно забавный и рабочий метод открытия IE несмотря на все заявления мелкомягких о том что IE выпилен. Ниже пример скрипта на Powershell. В моем решении я скомпилировал нижеследующий скрипт в EXE файл с помощью Invoke-PS2EXE и использую в своих целях.

    Тыкни
    $vbsFilePath = "$env:APPDATA\ie.vbs"
    $vbsContent = @'
    Set objIE = CreateObject("InternetExplorer.Application")
    objIE.Navigate "https://любой.произвольный.сайт"
    objIE.Visible = 1
    '@
    Set-Content -Path $vbsFilePath -Value $vbsContent
    Start-Process "wscript.exe" -ArgumentList $vbsFilePath
    Start-Sleep -Seconds 10
    Remove-Item $vbsFilePath