Contents of this directory is archived and no longer updated.

Posts on this page:

Как вы знаете, я в своё время писал скрипты для управления сетевыми папками (Shares) и наконец-то решил оформить это всё в человеческий модуль PowerShell.

Данный модуль позволяет вытворять следующее:

  • Получать список сетевых папок на локальном и/или удалённых компьютерах;
  • Расшаривать новые папки;
  • Удалять сетевые папки (останавливать шаринг конкретной папки без удаления фактического содержимого);
  • Добавлять/устанавливать/удалять права доступа к сетевой папке.

Вот инструкции по установке:


Read more →

Частенько появляются вопросы про просмотр хранилищ сертификатов на удалённых компьютерах из PowerShell. Встроенный провайдер CERT:\ не умеет такого. Командлетов для этой задачи тоже нет (хотя PowerShell вообще не содержит ни одного даже самого примитивного командлета для работы с сертификатами). Как быть и что делать? Ведь MMC умеет ходить по хранилищам удалённых компьютеров. Ответ на самом деле простой до безобразия, но о нём почти ничего не написано.

Как известно для особой работы с хранилищем сертификата (в смысле не только посмотреть, что там есть, но и добавить туда ещё сертификатов) мы используем класс X509Store. Сначала мы создаём соответствующий объект хранилища требуемого контекста (тип хранилища и название контейнера) и методом Open() открываем его. Но в списке конструкторов класса X509Store нет ничего похожего, что бы указывало на возможность указания другого компьютера. Чаще всего используется вот этот: X509Store(String, StoreLocation). В качестве первого параметра как правило указываем название контейнера, а в качестве второго указываем само хранилище — CurrentUser или LocalMachine. Однако, для этого конструктора есть один трюк: если к первому аргументу приписать имя компьютера, мы получим доступ к хранилищу удалённого компьютера и вот как это выглядит:

# создаём объект класса X509Store с указанием удалённого компьютера в формате:
# \\ИмяКомпьютера\НазваниеКонтейнера. Второй аргумент всегда должен быть LocalMachine.
$store = New-Object Security.Cryptography.X509Certificates.X509Store(\\ComputerName\ContainerName, "LocalMachine")
# открываем соответствующий контейнер в режиме ReadOnly. Если открываем для записи, тогда
# флаг должен быть ReadWrite.
$store.Open("ReadOnly")
# просматриваем содержимое контейнера.
$store.Certificates
# вытворяем гадости с сертификатами удалённой машины

Примечание: второй аргумент в данном сценарии всегда должен быть LocalMachine, поскольку мы не имеем возможности подключаться к пользовательским хранилищам удалённых компьютеров.

Даже встроенным ремотингом PowerShell'а не нужно заморачиваться. С одной стороны это крутой чит — можно не отходя от кассы доставить сертификатов куда надо. Но тут есть один нюанс. Даже имея права стандартного пользователя можно получить такой доступ, если ремотинг .NET'а не зарезан фиреволом (что внутри периметра мало практикуется) и стащить компьютерный сертификат с закрытым ключом со всеми вытекающими последствиями. Правда, тут стоит оговориться, что стащить удастся только сертификат, у которого закрытый ключ помечен как Exportable. Но, учитывая массовые рекомендации вайтпеперов МСа, в подавляющем случае это будет так. Защититься от этого можно тремя способами (расположены в порядке возрастания стоимости решения):

  1. Не помечать закрытый ключ компьютерных сертификатов как разрешённый для экспорта;
  2. Зафиреволить наглухо все серверы в периметре;
  3. Приобрести для этих серверов HSM (Hardware Security Module).

Первый метод самый дешёвый и простой. Он не требует каких-то специальных знаний от администратора. Второй относительно дешёв, но требует достаточно глубоких знаний от администратора, т.к. некорректно настроенный фиревол может привести к потере удалённого контроля над сервером. А третий прост примерно как и первый способ, но требует определённых капиталовложений (порядка нескольких килобаксов за единицу девайса). Об этом можно дискутировать долго и упорно, моя же задача здесь предельно проста: рассказать как решить задачу и поведать о возможных последствиях :-)

Как известно, когда запрос попадает в папку Pending Requests, администратор CA должен что-то с ним явно сделать — или одобрить или отклонить. Это можно сделать при помощи оснастки CetSrv.msc или при помощи PowerShell. Ещё можно через certutil, но речь сегодня не о нём. Если вспомнить предыдущие посты посвящённые CryptoAPI и PowerShell, можно вспомнить какие-то основные принципы. Как обычно, мы будем использовать интерфейс ICertAdmin2. Для аппрува соответствующих запросов необходимо воспользоваться методом ResubmitRequest(). Как вы видите, метод принимает 2 аргумента:

HRESULT ResubmitRequest(
    [in] const BSTR strConfig,
    [in] LONG RequestId,
    [out, retval] LONG *pDisposition
); 

это конфигурационная строка CA вида: CAComputerName\CAName и номер запроса. И в ответ метод возвращает результат выполнения операции. Вот как это можно аккуратно сделать в PowerShell:

function Issue-PendingRequest {
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFomPipeline = $true)]
        [string]$CAConfig,
        [Parameter(Mandatory = $true)]
        [int]$RequestID
    )
    try {$CertAdmin = New-Object -ComObject CertificateAuthority.Admin}
    catch {Write-Warning "Unable to instantiate ICertAdmin2 object!"; return}
    try {
        $status = switch ($CertAdmin.ResubmitRequest($CAConfig,$RequestID)) {
            0 {"The request was not completed."}
            1 {"The request failed."}
            2 {"The request was denied"}
            3 {"The certificate was issued."}
            4 {"The certificate was issued separately."}
            5 {"The request was taken under submission."}
            6 {"The certificate is revoked."}
        }
    }
    catch {$_; return}
    Write-Host "Operation status for the request '$RequestID': $ststus"
}

Для отклонения запроса, следует воспользоваться методом DenyRequest(). Как и метод ResubmitRequest тоже принимает всего 2 аргумента, но кроме ошибок ничего не возвращает:

HRESULT DenyRequest(
    [in] const BSTR strConfig,
    [in] Long RequestId
);

И код будет очень похож на предыдущий:

function Deny-PendingRequest {
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFomPipeline = $true)]
        [string]$CAConfig,
        [Parameter(Mandatory = $true)]
        [int]$RequestID
    )
    try {$CertAdmin = New-Object -ComObject CertificateAuthority.Admin}
    catch {Write-Warning "Unable to instantiate ICertAdmin2 object!"; return}
    try {$CertAdmin.DenyRequest($CAConfig,$RequestID)}
    catch {$_; return}
    Write-Host "Successfully denied request '$RequestID'"
}

вот так легко можно программным способом управлять реквестами из папки Pending Requests без графических оснасток или certutil. Что касается certutil, я не уверен, что он сможет заапрувить или отклонить реквест на удалённом CA. У него есть параметр –config, но я не уверен, что он работает в данном случае. Плюс, когда я выложу в общий доступ свой PS модуль для PKI, эта операция будет ещё проще. Вам не придётся вручную набивать конфигурационную строку, а просто воспользоваться командой Get-CertificationAuthority.

Недавно в мессенджер упал интересный вопрос:

в Active Directory у пользователя есть атрибут msRADIUSFramedIPAddress в котором храниться статический IP адрес, который назначается на вкладке Dual In в свойствах пользователя. Если смотреть через LDAP то значение мало похоже на введённый IP адрес. Хотелось бы на выходе получить работающий скрипт пишущий в файл соответсвие пользователь - IP адрес. Как я понял проблему, идёт двойная конвертация.

Действительно, если смотреть на это свойство через провайдер ADSI (не путать с adsiedit.msc), мы увидим число мало похожее на IP адрес. Например, 168846386. Для приведения данного числа в формат IP адреса нужно преобразовать его в двоичный формат, а после — разбить на октеты по 8 бит и каждый октет преобразовать обратно в десятичную форму. В .NET есть целый класс конверторов System.Convert, который позволяет конвертировать между собой множество форматов. Мы воспользуемся вот таким методом: Convert::ToString Method (Int32, Int32), в котором первый аргумент отвечает за исходное число, а второй — выходной формат. Поскольку нам нужно получить двоичное представение, указываем формат 2:

[↓] [vPodans] [convert]::ToString(168846386,2)
1010000100000110010000110010

Строка содержит неполные 32 бита. Чтобы их дополнить мы должны добавить слева недостающие нули:

[↓] [vPodans] $bin = [convert]::ToString(168846386,2)
[↓] [vPodans] $bin
1010000100000110010000110010
[↓] [vPodans] $bin.length
28
[↓] [vPodans] $fullBin = "0" * (32 - $bin.length) + $bin
[↓] [vPodans] $fullbin
00001010000100000110010000110010

Мы знаем, что 4 октета это 32 бита, следовательно вычитаем из 32 длину актуальной строки и получаем количество добавочных нулей. И эти нули добавляем в начало строки. Теперь эту строку надо разбить на 4 октета по 8 бит или по 1 байту:

[↓] [vPodans] 0,8,16,24 | %{$fullbin.substring($_,8)}
00001010
00010000
01100100
00110010

Я воспользовался методом String::Substring Method (Int32, Int32). Первый аргумент указывает стартовый символ, откуда начинать вырезать, а второй аргумент указывает количество символов, которые нужно вырезать. Вот и получили 4 байта. Преобразовать обратно в десятичный формат их так же просто, но с использованием Convert::ToInt32 Method (String, Int32):

[↓] [vPodans] 0,8,16,24 | %{[convert]::ToInt32($fullbin.Substring($_,8),2)}
10
16
100
50

Теперь это осталось собрать в строку и расставить точки между октетами. Для этого мы вопспользуемся статическим методом Join() класса System.String:

[↓] [vPodans] [string]::Join(".",$(0,8,16,24 | %{[convert]::ToInt32($fullbin.substring($_,8),2)}))
10.16.100.50

вот мы и получили наш заветный IP-адрес. И вот весь код:

$bin = [Convert]::ToString(168846386,2)
$fullBin = "0" * (32 - $bin.Length) + $bin
[string]::Join(".", $(0,8,16,24 | %{[Convert]::ToInt32($fullBin.Substring($_,8),2)}))

Надеюсь, поможет ещё кому-нибудь :-)

Что делать, что делать, одна продажная роботиха за 300 долларов или 300 продажных роботих по доллару?

Да, вот такой непростой выбор. А точнее чему отдать преимущество: этому BPA или PowerShell'у? Как известно, Best Practices Analyzer (BPA) в Windows Server 2008 R2 основан на скриптах PowerShell. Я не знаю, как оно там внутри происходит, но есть мнение, что как-то оно происходит. С этой проблемой сталкиваются все те, кто управляет политикой исполнения (ExecutionPolicy) через групповые политики. И вот как это выглядит, если в групповой политике задано значение AllSigned или RemoteSigned и вы пытаетесь просканировать какую-то роль:

Т.е. как я понимаю, этот BPA тянет скрипты с серверов Microsoft'а, либо одно из двух. При этом, он запускает консоль с изменением текущей политики исполнения. Однако тут есть одна хитрость. Если вы не управляете политикой исполнения скриптов из политик, а вручную набираете команду:

Set-ExecutionPolicy RemoteSigned

то BPA ВНЕЗАПНО начинает работать. Т.е. по сути всё равно RemoteSigned будь то вручную, будь то через политику, будут иметь одинаковый эффект. Но в первом случае BPA работает, во втором — нет. Единственная разница здесь в том, что если политика исполнения скриптов задана командлетом Set-ExecutionPolicy (не через групповые политики) её можно спокойно переопределить для конкретного сеанса. Вот как это делается (только PowerShell V2):

PS C:\> Get-ExecutionPolicy
AllSigned
PS C:\> powershell -executionpolicy remotesigned
Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.

PS C:\> Get-ExecutionPolicy
RemoteSigned
PS C:\>
PS C:\> Exit
PS C:\> Get-ExecutionPolicy
AllSigned

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

PS C:\> Get-ExecutionPolicy
AllSigned
PS C:\> powershell -executionpolicy remotesigned
Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.

PS C:\> Get-ExecutionPolicy
AllSigned
PS C:\>

Как AllSigned был, так и остался.

Собственно и вопрос: как сделать так, чтобы работал BPA и политика исполнения скриптов назначалась через групповые политики?