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

  1. Проверить наличие и статус (включена/отключена) учетной записи пользователя.
  2. Проверить включена ли учетная запись в группу «Администраторы»
  3. Если учетная запись отсутствует, то создать учетную запись и добавить ее в группу администраторы, проставить флаги «Запретить смену пароля пользователем» и «Срок действия пароля не ограничен»
  4. Если учетная запись существует, но отключена либо не входит в группу «Администраторы», то включить учетную запись и добавить ее в группу «Администраторы», проставить флаги «Запретить смену пароля пользователем» и «Срок действия пароля не ограничен»
  5. Скрипт не должен зависеть от языка операционной системы.


Я подумал, что ничего сложного и первым делом решил проверить есть пользователь Admin на моем компьютере. Недолго думая, я вбил команду:

Get-User admin

И тут же получил ошибку

Get-User : Имя "Get-User" не распознано как имя командлета, функции, файла сценария или выполняемой программы.

Немного опешив, от того, что такая полезная команда и не распознала я погуглил и обнаружил, что команда Get-User работает только в консоли Powershell для Exchange. Для работы с локальными пользователями необходимо использовать Get-Localuser, а для доменных Get-Aduser. Осознав свою ошибку, я вбил:

Get-Localuser admin

И получил ответ

Name  Enabled Description
----  ------- -----------
Admin True    Встроенная учетная запись администратора компьютера/домена

Ну теперь проверить есть пользователь или нет, не составит большого труда, однако и тут меня ждал очередной подвох. Через условный оператор if else сделать это оказалось не так-то просто.

Дело в том, что если пользователь есть в системе, то на выход мы получаем не значение true, а целый набор данных, с именем, описанием пользователя, включена эта учетная запись или нет. А если пользователя нет, то powershell выдает ошибку. Побродив по просторам интернета, я обнаружил, что для этих целей лучше использовать оператор try сatch.

$user = 'admin'
try {
    Get-LocalUser $user -ErrorAction Stop | Out-Null
    write-host пользователь $user есть -foregroundcolor Green
    }
Catch {
    write-host пользователя $user нет -foregroundcolor Red
    }

Я добавил переменную $user, чтобы было проще менять имена пользователя во всем скрипте. ErrorAction Stop необходим, чтобы скрипт не прервался на этом шаге из-за ошибки. Знак | разделяет шаги конвейера, а Out-Null скроет вывод текста ошибки. Так же я добавил вывод текста с подсветкой, для удобства проверки скрипта.

Далее я захотел проверить, включена или отключена учетная запись. Как я уже говорил, команда Get-LocalUser выдает целый набор данных и для проверки, мне нужно было выделить только один параметр Enabled со значением true или false. Для этого я воспользовался следующей командой:

(Get-LocalUser $user).enabled

Убедившись, что он работает, я сделал следующую проверку и добавил команду включения пользователя.

$user = 'admin'
        if ((Get-LocalUser $user).enabled -eq "True") {
        write-host учетная запись включена -foregroundcolor Green
        }
        else {
        write-host учетная запись отключена -foregroundcolor Red
        Enable-LocalUser $user
        write-host учетная запись включена -foregroundcolor Green
        }

Для включения пользователя, естественно необходимо запускать консоль под правами администратора.

Во втором пункте задания, необходимо было определить входит ли пользователь в группу администраторов. Для проверки наличия пользователя в той или иной группе есть команда Get-LocalGroupMember. Однако имя группы администраторов, меняется от языка операционной системы. Поэтому мне пришлось воспользоваться стандартным SID S-1-5-32-544. Проверку я также сделал через try сatch.

$user = 'admin'
        try {
        Get-LocalGroupMember -SID S-1-5-32-544 -Member $user -ErrorAction Stop | Out-Null
        write-host пользователь $user состоит в группе администраторы -foregroundcolor Green
        }
        Catch {
        write-host пользователь $user не состоит в группе администраторы  -foregroundcolor Red
        Add-LocalGroupMember -SID S-1-5-32-544 -Member $user
        write-host пользователь $user состоит в группе администраторы -foregroundcolor Green
        }

В случае, если пользователь не состоит в группе, администраторов он будет в нее добавлен.
На следующем этапе, у меня стояла задача определить отключена настройка смены пароля у пользователя или нет. Тут я наткнулся на очередные подводные камни. Дело в том, что если у пользователя, срок действия пароля не ограничен, то параметр PasswordExpires не выдает никаких значений. А если эта галочка отключена, то у разных пользователей будет стоять разная дата смены пароля. Выход из этого положения я всё-таки придумал:

if ((Get-LocalUser $user).PasswordExpires -eq $null) {
        write-host срок действия паролья не ограничен -foregroundcolor Green
        }
        else {
        write-host срок действия пароля ограничен -foregroundcolor Red
        set-LocalUser $user -PasswordNeverExpires:$true
        write-host срок действия пароля не ограничен -foregroundcolor Green
        }

Дальше мне было необходимо создавать пользователя. Я никак не ожидал, что и здесь меня может ждать подвох. При создании пользователя командой

new-LocalUser -User $user  -password P@ssW0rD!

Возвращается ошибка:

Не удается привязать параметр "Password". Не удается преобразовать значение "P@ssW0rD!" типа "System.String" в тип "System.Security.SecureString".

Дело в том, что параметр -password должен использовать SecureString, вместо обычной текстовой строки. Для этого я сделал переменную $password и сконвертировал её в securestring.

$password = convertto-securestring "P@ssW0rD!" -asplaintext -force

А сам скрипт по созданию пользователя и добавления его в группу администраторов

new-LocalUser -User $user  -password $password -PasswordNeverExpires:$true -AccountNeverExpires:$true
    Add-LocalGroupMember -SID S-1-5-32-544 -Member $user

Готовый скрипт учитывающий все условия у меня получился таким образом:

$user = 'admin'
$password = convertto-securestring "P@ssW0rD!" -asplaintext -force
try {
    Get-LocalUser $user -ErrorAction Stop | Out-Null
    cls
    write-host пользователь $user есть -foregroundcolor Green    
        if ((Get-LocalUser $user).enabled -eq "True") {
        write-host учетная запись включена -foregroundcolor Green
        }
        else {
        write-host учетная запись отключена -foregroundcolor Red
        Enable-LocalUser $user
        write-host учетная запись включена -foregroundcolor Green
        }
        if ((Get-LocalUser $user).PasswordExpires -eq $null) {
        write-host срок действия паролья не ограничен -foregroundcolor Green
        }
        else {
        write-host срок действия пароля ограничен -foregroundcolor Red
        set-LocalUser $user -PasswordNeverExpires:$true
        write-host срок действия паролья не ограничен -foregroundcolor Green
        }
        try {
        Get-LocalGroupMember -SID S-1-5-32-544 -Member $user -ErrorAction Stop | Out-Null
        write-host пользователь $user состоит в группе администраторы -foregroundcolor Green
        }
        Catch {
        write-host пользователь $user не состоит в группе администраторы  -foregroundcolor Red
        Add-LocalGroupMember -SID S-1-5-32-544 -Member $user
        write-host пользователь $user состоит в группе администраторы -foregroundcolor Green
        }
    }    
Catch {
    cls
    write-host пользователя $user нет -foregroundcolor Red
    new-LocalUser -User $user  -password $password -PasswordNeverExpires:$true -AccountNeverExpires:$true
    Add-LocalGroupMember -SID S-1-5-32-544 -Member $user
    write-host пользователь $user создан и добавлен в группу администраторов -foregroundcolor Green
    }

Post Scriptum

Однако хранение пароля администратора в скрипте — это не лучшая практика с точки зрения безопасности. Для этого можно использовать хэш пароля, а не сам пароль.

$user = 'admin1'
$hash = "01000000d08c9ddf0115d1118c7a00c04fc297eb0100000048baf1b47a72d845b4eeda8659da9fd70000000002000000000010660000000100002000000004d2a3bf03aac92c2e172dc002d1fe8759da4655850462aface5c25f52921e05000000000e8000000002000020000000bfa652b6f7cc6845e3cee1831b54ab93dc272d0b2203024c3de8bc4789a2698a10000000923fb50dcf01125913534d0c1ba6d827400000002757df7d8a8b39c8e84b81323acff1d72419580a0022cf610926e5f081a2e895320088d482eb407f8157aad82f091abee7427b357e871d12238a8f74131238e7"
$hash
$password = ConvertTo-SecureString -String $hash
$password
 new-LocalUser -User $user  -password $password -PasswordNeverExpires:$true -AccountNeverExpires:$true
Add-LocalGroupMember -SID S-1-5-32-544 -Member $user

Для того, чтобы получить хэш пароля можно воспользоваться следующим скриптом

$Secure = Read-Host -AsSecureString
$Secure
$hash = ConvertFrom-SecureString -SecureString $Secure
$hash

Который запросит ввод пароля с клавиатуры.

И маленький бонус для вывода всех отключенных записей

Get-LocalUser | Where-Object {$_.enabled -eq $false}

Надеюсь, потраченное мною время поможет кому-то в работе. Да и для себя будет полезно не забыть этот бесценный опыт.

Комментарии (35)


  1. Daysleeper Автор
    01.08.2018 03:11
    -3

    Кстати, сегодня узнал, что в работе мне отказали :( Готов выслать резюме в личку, если кого интересует в Питере.


  1. click0
    01.08.2018 04:22
    +2

    Добавьте разметку кода. У меня глаза чуть не потекли. :(


  1. vesper-bot
    01.08.2018 07:23

    А всё потому, что кто-то не читает Get-Help на команды!


    1. muon
      01.08.2018 07:28

      man man


  1. muon
    01.08.2018 07:27
    +2

    Честно говоря, не вяжется «неплохо разбираюсь в powershell» с откровениями про пайп (|). Объектный пайп — самое крутое, что есть в Powershell. И вот это

    на выход мы получаем не значение true, а целый набор данных
    — ожидаемое поведение для любого Get-Wtf. С ними прикольно другое, если объектов (пользователей) 2 и более, то командлет возвращает массив объектов. А если объект один, то возвращает сам объект (а не массив из одного объекта), ломая к чёртовой бабушке всю последующую логику. Рождаются костыли:
    $shrimps = @()
    $shrimps += Get-Shrimps
    и дальше смело работаем как с массивом.


    1. mayorovp
      01.08.2018 08:41

      Можно проще: $shrimps = @(Get-Shrimps)


      Или вот так:


      Get-Shrimps |% {
          $shrimp = $_
      
          # ...
      }


    1. Daysleeper Автор
      01.08.2018 09:47

      Честно говоря, не вяжется «неплохо разбираюсь в powershell» с откровениями про пайп (|)
      Я знал, что такое конвейер до написания этой статьи. Хотел максимально подробно разъяснить все детали, для тех пользователей, кто недавно начал работать с powershell. Но не учёл высокий уровень читателей Хабра и сейчас откровенно жалею за потраченное мной время на написание статьи.
      С ними прикольно другое, если объектов (пользователей) 2
      В моих примерах везде явно задан конкретный пользовать. Поэтому возвращать будет либо информацию о конкретном пользователе, либо ошибку если пользователя в системе отсутсвует.
      Массив данных можно получить только с помощью команды из дополнения Get-LocalUser | Where-Object {$_.enabled -eq $false}

      В любом случае, спасибо за уточнение.


  1. SAGSa
    01.08.2018 09:57

    По умолчанию ваш скрипт будет работать только на windows server 2016 и windows 10 версии выше 1607, так как модуль Microsoft.PowerShell.LocalAccounts в котором содержится командлет Get-Localuser есть только на этих системах. Наверное для решения задачи лучше было попробовать использовать wmi.


    1. Daysleeper Автор
      01.08.2018 10:06

      Спасибо за уточнение. Я действительно забыл уточнить что писал на powershell 5.1


    1. zigrus
      01.08.2018 10:18

      выполнил Get-Localuser в Win7
      все получилось


      1. SAGSa
        01.08.2018 10:21

        Значит у вас установлен powershell 5.1 либо модуль Microsoft.PowerShell.LocalAccounts.


      1. Daysleeper Автор
        01.08.2018 10:22

        Проверьте версию powershell на своей системе.

        $psversiontable.psversion


        Возможно на системе powershell обновлен.


        1. zigrus
          01.08.2018 10:33

          да, 5.1


  1. zigrus
    01.08.2018 10:06
    -2

    как можно средствами powershell создать почтовые ящики пользователям на exchange 2003


    1. Daysleeper Автор
      01.08.2018 10:09

      насколько я знаю, Exchange начал поддерживать powershell с версии 2010.


      1. zigrus
        01.08.2018 10:10

        есть там powershell


        1. Daysleeper Автор
          01.08.2018 12:55

          на Хабре пишут, что Exchange Management Shell поддерживает с 2007 habr.com/post/130640


    1. Hikedaya
      01.08.2018 22:08

      Например, вот так:

      Function CreateNewUserMailbox($objUser, $MakeHidden, $mdb) {
      	$CreateNewUserMailbox = $False
      	$objUser.CreateMailbox($mdb)
      	$objUser.Put("msExchUserAccountControl", 0)
      	$objUser.SetInfo()
      	if ($MakeHidden) {
      		$objUser.Put("msExchHideFromAddressLists", $MakeHidden)
      		$objUser.SetInfo()
      	}
      }
      $userDN = (get-qaduser $userLogin).directoryentry.distinguishedname
      $user = [adsi]"LDAP://$userDN"
      $mdbName = "CN=Store1,CN=SG1,CN=InformationStore,CN=EXCH-01,CN=Servers,CN=Org,CN=Administrative Groups,CN=Org,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=example,DC=com"}
      CreateNewUserMailbox $user $false $mdbName

      Пусть не смущает командлет Get-QADUser — в те времена я использовал PowerGUI.


  1. paranoya_prod
    01.08.2018 12:00

    <Зануда on>
    Зачем проверять значение «срок действия пароля не ограничен», если его всё равно нужно устанавливать? Этим мы сократим код скрипта и время выполнения. :)
    То же самое и с группой, а ошибку вторичного добавления юзера в группу можно просто игнорировать.


    1. Daysleeper Автор
      01.08.2018 12:33

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


  1. Busla
    01.08.2018 12:34
    +1

    странные у вас «просторы интернета», наличие/отсуствие объекта элементарно проверяется в if:

    if (Get-LocalUser 'admin' -ErrorAction SilentlyContinue) { Write-Host 'Есть пользователь'} else {Write-Host 'Нет пользователя' }


    1. SAGSa
      01.08.2018 13:22
      +4

      Все таки правильнее обрабатывать ошибки а не скрывать их. В вашем случае скрипт будет выдавать «нет пользователя» если произойдет любая ошибка в Get-LocalUser и он ничего не вернет. Использование try{}catch{}, более оправдано… Правильнее было бы

      $user="admin"
      try 
      {
          Get-LocalUser $user -ErrorAction Stop
          write-host пользователь $user есть -foregroundcolor Green
      }
      Catch 
      {
          if ($_.FullyQualifiedErrorId -eq "UserNotFound,Microsoft.PowerShell.Commands.GetLocalUserCommand")
          {
              write-host "Пользователь $User не найден" -foregroundcolor Red
          }
          else
          {
              Write-Error $_
          }
          
      }


      1. mayorovp
        01.08.2018 13:27

        Тогда уж проще if (Get-LocalUser |? {$_.Name -eq $user}) написать.


      1. Busla
        01.08.2018 15:16

        Пожалуй, я соглашусь с вашим основном тезисом, но предпочту решение mayorovp


  1. mixeon
    01.08.2018 14:07

    Нуу… нет, все можно было сделать намного проще и понятнее. Вместо try/catch вполне удобно использовать параметр -ErrorAction. А «хэш» пароля не будет корректно декодироваться под другой учетной записью (другом пк). Чтобы он «работал» везде, нужно использовать параметр key. Самый простой вариант, это запросить прямо из консоли новый пароль. И по итогу весь скрипт будет выглядеть примерно так:

    $userName = "Admin"
    
    if (-not (Get-LocalUser $userName -ErrorAction SilentlyContinue))
    {
        #Если У/З отсутствует, то будет создана
        $pass = Read-Host "Inpur new password for $userName" -AsSecureString
        New-LocalUser -Name $userName -Password $pass -UserMayNotChangePassword -PasswordNeverExpires
    }
    
    #Раз У/З есть (а если не было, то уже есть), то можем создать объект аккаунта, с ним удобнее
    $Account = Get-LocalUser $userName
    
    #Включаем аккаунт, если отключен
    if (-not $Account.Enabled) { Enable-LocalUser $Account }
    
    #Если аккаунт не входит в группу администраторов, то добавим
    if (-not (Get-LocalGroupMember -Member $Account -SID S-1-5-32-544 -ErrorAction SilentlyContinue ))
    {
        Add-LocalGroupMember -SID S-1-5-32-544 -Member $Account
    }
    
    #Задаем параметры «Запретить смену пароля пользователем» и «Срок действия пароля не ограничен»
    Set-LocalUser $Account -PasswordNeverExpires $true -UserMayChangePassword $false 
    


    1. DaMaNic
      01.08.2018 16:30
      +3

      Кажется Вы только что объяснили почему топикстартера не взяли на работу…


  1. Xeonkeeper
    01.08.2018 17:28

    Запретить смену пароля текущим членам группы администраторов невозможно — ругается.
    Пришлось сначала удалять, прописывать это свойство, а потом заново добавлять. Сделал вообще без проверок и с глобальным игнором ошибок.

    $ErrorActionPreference = 'SilentlyContinue'
    $username = "Admin"
    $AdminGroup = "S-1-5-32-544"
    $getuser = Get-LocalUser $username
    if ($getuser -eq $null)
        {
        $Password = Read-Host -AsSecureString
        New-LocalUser -Name $username `
        -PasswordNeverExpires `
        -UserMayNotChangePassword  `
        -Password $Password
        Add-LocalGroupMember -SID $AdminGroup -Member $username
        }
    else
        {
        Remove-LocalGroupMember -Member $username -SID $AdminGroup
        Set-LocalUser -Name $username `
        -PasswordNeverExpires $true `
        -UserMayChangePassword $false `
        -Password $Password
        Add-LocalGroupMember -SID $AdminGroup -Member $username
        Enable-LocalUser $username
        }


    1. Daysleeper Автор
      01.08.2018 17:36

      консоль под админом запускаете?
      только что перепроверил

      set-LocalUser admin -PasswordNeverExpires:$true

      галочка по сроку действую пароля устанавливается.


      1. Xeonkeeper
        01.08.2018 17:44

        С этим проблем нет, проблемы есть со свойством смены пароля, а не его сроком действия.
        попробуйте

        Set-LocalUser -Name admin -UserMayChangePassword $false


        1. Daysleeper Автор
          01.08.2018 18:05

          Да, действительно ругается, на одного администратора в системе.
          Попробуйте через конвейер это сделать.

          $password = convertto-securestring "P@ssW0rD!" -asplaintext -force
          Set-LocalUser -Name admin -Password $Password | Set-LocalUser -UserMayChangePassword $false


          1. Xeonkeeper
            01.08.2018 18:11

            А что собственно вы пытаетесь передать по этому пайпу?
            PS, не выйдет никак. Даже через GUI он ругается на ограничения. Возможно этот нюанс тоже повлиял на оценку тестового задания.


            1. Daysleeper Автор
              01.08.2018 18:15

              может сначала ответите, у вас сработало или нет?


              1. Xeonkeeper
                01.08.2018 18:23
                +1

                Я специально для вас проверил заведомо не рабочее решение и нет, оно не сработало.


            1. Daysleeper Автор
              01.08.2018 18:22

              да, действительно. даже через консоль этот параметр не изменить, если в системе остался только один администратор.


              1. Xeonkeeper
                01.08.2018 18:25

                не работает с любым количеством