Contents of this directory is archived and no longer updated.

Posts on this page:

Не каждый знает, что центр сертификации Windows поддерживает не одну, а несколько таблиц:

  1. Таблица запросов (что мы видим в оснастке certsrv.msc);
  2. Таблица списков отзывов (CRL Table);
  3. Таблица атрибутов;
  4. Таблица расширений.

Мы уже знаем, как обращаться к таблице запросов. По схожему принципу можно обращаться и к другим таблицам. Для переключения между ними используется метод SetTable интерфейса ICertView2. По умолчанию всегда используется таблица CVRC_TABLE_REQCERT. Вот какие кодовые номера у таблиц:

  • CVRC_TABLE_ATTRIBUTES = 0x4000
  • CVRC_TABLE_CRL = 0x5000
  • CVRC_TABLE_EXTENSIONS = 0x3000
  • CVRC_TABLE_REQCERT = 0

Метод SetTable нужно вызывать сразу после вызова метода OpenConnection. Таблица CRL'ов хранит историю всех CRL'ов (вместе с самими CRL'ами), которые были сгенерированы сервером CA, а так же прочую полезную информацию.

Вот как смотрится схема этой таблицы:

$CA = "dc1\Contoso CA"
$CaView = New-Object -ComObject CertificateAuthority.View
# открываем подключение к CA
$CaView.OpenConnection($CA)
# указываем необходимую таблицу
$CaView.SetTable(0x5000)
# говорим, что мы хотим посмотреть схему
$Columns = $CaView.EnumCertViewColumn(0)
# начинаем итерацию по столбцам БД
[void]$Columns.Next()
do {
    # создаём временный объект, чтобы получить красивый вывод и наполняем его данными
    $Column = "" | Select Name, DisplayName, Type, MaxLength
    $Column.Name = $Columns.GetName()
    $Column.DisplayName = $Columns.GetDisplayName()
    $Column.Type = switch ($Columns.GetType()) {
        1 {"Long"}
        2 {"DateTime"}
        3 {"Binary"}
        4 {"String"}
    }
    [string]$Column.MaxLength = $Columns.GetMaxLength()
    if ($Columns.IsIndexed() -eq 1) {$Column.MaxLength += ", Indexed"}
    $Column
} until ($Columns.Next() -eq -1)
# закрываем подключение к БД
$Columns.Reset()

Её вид:

Name                          DisplayName                   Type                          MaxLength
----                          -----------                   ----                          ---------
CRLRowId                      CRL Row ID                    Long                          4, Indexed
CRLNumber                     CRL Number                    Long                          4, Indexed
CRLMinBase                    CRL Minimum Base Number       Long                          4
CRLNameId                     CRL Name ID                   Long                          4
CRLCount                      CRL Count                     Long                          4
CRLThisUpdate                 CRL This Update               DateTime                      8
CRLNextUpdate                 CRL Next Update               DateTime                      8, Indexed
CRLThisPublish                CRL This Publish              DateTime                      8
CRLNextPublish                CRL Next Publish              DateTime                      8, Indexed
CRLEffective                  CRL Effective                 DateTime                      8
CRLPropagationComplete        CRL Propagation Complete      DateTime                      8, Indexed
CRLLastPublished              CRL Last Published            DateTime                      8, Indexed
CRLPublishAttempts            CRL Publish Attempts          Long                          4, Indexed
CRLPublishFlags               CRL Publish Flags             Long                          4
CRLPublishStatusCode          CRL Publish Status Code       Long                          4, Indexed
CRLPublishError               CRL Publish Error Information String                        8192
CRLRawCRL                     CRL Raw CRL                   Binary                        536870912

Вот пример вывода:

$CA = "dc1\Contoso CA"
$CaView = New-Object -ComObject CertificateAuthority.View
$CaView.OpenConnection($CA)
$CaView.SetTable(0x5000)
$ColumnCount = $CaView.GetColumnCount(0)
$CaView.SetResultColumnCount($ColumnCount)
0..($ColumnCount - 1) | %{$CAView.SetResultColumn($_)}
$Row = $CaView.OpenView()
[void]$Row.Next()
while ($Row.Next() -ne -1) {
    $cert = New-Object psobject
    $Column = $Row.EnumCertViewColumn()
    while ($Column.Next() -ne -1) {
        $current = $Column.GetName()
        $Cert | Add-Member -MemberType NoteProperty $($Column.GetDisplayName()) -Value $($Column.GetValue(1)) -Force
    }
    $Cert
    $Column.Reset()
}
$Row.Reset()
CRL Row ID                    : 409
CRL Number                    : 347
CRL Minimum Base Number       : 340
CRL Name ID                   : 1
CRL Count                     : 0
CRL This Update               : 2010.02.25. 17:55:51
CRL Next Update               : 2010.02.26. 19:15:51
CRL This Publish              : 2010.02.25. 18:05:51
CRL Next Publish              : 2010.02.26. 18:05:51
CRL Effective                 : 2010.02.18. 17:55:51
CRL Propagation Complete      : 2010.02.25. 19:05:51
CRL Last Published            : 2010.02.25. 18:05:51
CRL Publish Attempts          : 1
CRL Publish Flags             : 6
CRL Publish Status Code       : 0
CRL Publish Error Information : -
CRL Raw CRL                   : MIICzDCCAbQCAQEwDQYJKoZIhvcNAQEFBQAwQzETMBEGCgmSJomT8ixkARkWA2Nv
                                bTEXMBUGCgmSJomT8ixkARkWB2NvbnRvc28xEzARBgNVBAMTCkNvbnRvc28gQ0EX
                                DTEwMDIyNTE3NTU1MVoXDTEwMDIyNjE5MTU1MVqgggE7MIIBNzAfBgNVHSMEGDAW
                                gBQSyac4taTAA9cqtmWlsPEzGBKsPzAQBgkrBgEEAYI3FQEEAwIBATALBgNVHRQE
                                BAICAVswHAYJKwYBBAGCNxUEBA8XDTEwMDIyNjE4MDU1MVowDgYDVR0bAQH/BAQC
                                AgFUMIHGBgkrBgEEAYI3FQ4EgbgwgbUwgbKgga+ggayGgalsZGFwOi8vL0NOPUNv
                                bnRvc28lMjBDQSxDTj1EQzEsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZp
                                Y2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9Y29udG9zbyxEQz1j
                                b20/ZGVsdGFSZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3Ry
                                aWJ1dGlvblBvaW50MA0GCSqGSIb3DQEBBQUAA4IBAQB3DuZRToyK/OkrnCQQUl1P
                                qgv/V9nPhAR6LF/b25Tq7fhKZniAk/cPgj2L3IMsLx2lmrI7GEEDt4UDcuc3EPtF
                                f7gixrN3K+eSE/Er0NxBdJFUhHK9e/CVXqJFV2pGSa77mvcI75l2h5mlUGDZzGOl
				<...>
<...>

Что касается остальных двух таблиц, то они, как я уже говорил, работают по тому же принципу и вы так же можете накладывать фильтры вывода методом SetRestriction. Они содержат ту же информацию, что вы видите в оснастке CertSrv.msc, когда выделяете запрос, нажимаете Action –> All Tasks –> View Attributes/Extensions.

На этом я завершаю цикл статей по работе с базой данных центров сертификации Windows с помощью Windows PowerShell.

В предыдущих статьях (раз и два) мы говорили о том, как получить схему БД центра сертификации и как получать определённые свойства каждой записи в БД. Но очень часто нам нужно будет ограничивать вывод БД по каким-то критериям. Например, мы можем хотеть получать сведения о запросах на сертификат, которые выданы на определённое имя (common name). А можем и хотеть получать сведения о ещё не одобренных запросах (хранящихся в папке Pending Requests). А может хотим отфильтровать вывод по дате, когда заканчивается срок действия сертификата. Вобщем, хотелок может быть очень много. Самый простой вариант фильтрации — получить все строки из БД, а потом через Where-Object отфильтровать нужные. Практически во всех случаях это будет плохим решением, потому что оно медленное, создаёт нагрузку на БД и очень ресурсоёмким.

Решение этой проблемы мы можем возложить на COM интерфейсы, которые будут самостоятельно извлекать только те данные, которые нам нужны (подпадают под определённый фильтр). Для этого у ICertView2 есть метод SetRestriction:

CCertView.SetRestriction( _
  ByVal ColumnIndex, _
  ByVal SeekOperator, _
  ByVal SortOrder, _
  ByVal pvarValue _
)

В свойстве ColumnIndex мы указываем номер колонки или название таблицы. Если мы захотим посмотреть запросы сертификатов, находящиеся в папке Pending Requests мы можем указать таблицу CV_COLUMN_QUEUE_DEFAULT, а если только отклонённые/ошибочные запросы, то таблицу CV_COLUMN_LOG_FAILED_DEFAULT. Вот значения для этих параметров:

  • CV_COLUMN_QUEUE_DEFAULT = –1
  • CV_COLUMN_LOG_DEFAULT = –2
  • CV_COLUMN_LOG_FAILED_DEFAULT = –3

Как видите, при использовании отрицательных значений мы задаём фильтр на уровне таблиц. Если хотим фильтровать на уровне столбцов (т.е. значение определённого столбца соответствует чему-то), значение этого аргумента должно быть натуральным и это значение должно быть равно номеру столбца. Как мы уже знаем, номер столбца можно получить при помощи метода GetColumnIndex.

Дальнейшие аргументы имеют смысл только если ColumnIndex является натуральным числом. SeekOperator задаёт уровень сравнения и они достаточно понятно расписаны в таблице. Единственное, что тут хочу отметить — это числовые значения операторов сравнения:

  • CVR_SEEK_NONE = 0x0 (должно быть указано, если ColumnIndex не является натуральным числом)
  • CVR_SEEK_EQ = 0x1
  • CVR_SEEK_LT = 0x2
  • CVR_SEEK_LE = 0x4
  • CVR_SEEK_GE = 0x8
  • CVR_SEEK_GT = 0x10

Далее идёт аргумент, задающий порядок сортировки:

  • CVR_SORT_NONE = 0
  • CVR_SORT_ASCEND = 1
  • CVR_SORT_DESCEND = 2

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

Возьмём пример, мы хотим получить все строки БД, у которых столбец Request Common Name равен "contoso-dc2-ca" (сертификаты выданные подчинённому CA). Для этого в ColumnIndex укажем 60 (номер столбца CommonName), в SeekOperator укажем 1, а в pvarValue укажем значение, которому должно оно соответствовать: contoso-dc2-ca.

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

$CaView = New-Object -ComObject CertificateAuthority.View
$CaView.OpenConnection("dc1\contoso ca")
$properties = "RequestID","RequesterName","CommonName","NotBefore","NotAfter","SerialNumber"
$CaView.SetResultColumnCount($properties.Count)
$properties | %{$CAView.SetResultColumn($CAView.GetColumnIndex($False, $_))}

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

И сам фильтр:

# получаем номер столбца, по которому будет производиться фильтрация
$RColumn = $CAView.GetColumnIndex($False, "CommonName")
# устанавливаем сам фильтр
$CaView.SetRestriction($RColumn,1,0,"contoso-dc2-ca")

По умолчанию фильтр устанавливается на таблице CV_COLUMN_LOG_DEFAULT, которая содержит запросы сертификатов, содержащихся в папках Revoked Certificates, Issued Certificates и Failed Requests. Мы не можем в пределах одного фильтра указать конкретную таблицу. Но вы можете использовать несколько фильтров одновременно и каждый из них будет обрабатываться по очереди с применением логического оператора И. Т.е. в результате мы получим только те строки БД, которые подпадают под каждый фильтр. Если строка не соответствует какому-то фильтру, она будет отфильтрована и не будет отображена на выходе. Поэтому я вам покажу ещё один вариант полезного фильтра, который фильтрует строки БД по папкам (Revoked Certificates, Issued Certificates, Pending Requests, FailedRequests). В БД есть такой столбец под названием Disposition. Этот столбец имеет тип Long и вот какие полезные значения может принимать:

  • 9 – запрос ожидает одобрения (находится в папке Pending Requests);
  • 12 – архивированный сторонний сертификат;
  • 15 – обновление сертификата CA;
  • 16 – цепочка сертификатов текущего CA;
  • 20 – сертификат успешно выдан (находится в папке Issued Certificates);
  • 21 – сертификат отозван (находится в папке Revoked Certificates);
  • 30, 31 — запросы, хранящиеся в папке Failed Requests.

Поэтому если мы хотим получить выданные и неотозванные сертификаты на имя contoso-dc2-ca, мы создадим ещё один фильтр по столбцу Disposition (и значением равным 20):

$RColumn = $CAView.GetColumnIndex($False, "Disposition")
$CaView.SetRestriction($RColumn,1,0,20)

Если мы хотим добавить фильтр, что сертификат должен быть действующий, т.е. столбец NotAfter должен быть больше, чем текущие дата и время:

$RColumn = $CAView.GetColumnIndex($False, "NotAfter")
$CaView.SetRestriction($RColumn,0x10,0,[datetime]::Now)

Когда с фильтрами покончено, можно начинать шахматы:

$Row = $CaView.OpenView()
while ($Row.Next() -ne -1) {
    $cert = New-Object psobject
    $Column = $Row.EnumCertViewColumn()
    while ($Column.Next() -ne -1) {
        $current = $Column.GetName()
        $Cert | Add-Member -MemberType NoteProperty $($Column.GetDisplayName()) -Value $($Column.GetValue(1)) -Force
    }
    $Cert
    $Column.Reset()
}
$Row.Reset()

и что мы получили на выходе:

Issued Request ID           : 21
Requester Name              : CONTOSO\Administrator
Issued Common Name          : contoso-DC2-CA
Certificate Effective Date  : 2009.03.30. 13:56:53
Certificate Expiration Date : 2011.03.30. 14:06:53
Serial Number               : 6127fbc7000000000015

Issued Request ID           : 40
Requester Name              : CONTOSO\Administrator
Issued Common Name          : contoso-DC2-CA
Certificate Effective Date  : 2010.03.06. 10:56:53
Certificate Expiration Date : 2015.03.05. 10:56:53
Serial Number               : 158f4a3b000100000028

Issued Request ID           : 42
Requester Name              : CONTOSO\Administrator
Issued Common Name          : contoso-DC2-CA
Certificate Effective Date  : 2010.03.06. 11:10:31
Certificate Expiration Date : 2015.03.05. 11:10:31
Serial Number               : 159bc07a00010000002a

т.е. мы получили только те строки БД, которые подпали под каждый указанный фильтр. Если запустить этот же код 1 апреля, мы уже не получим строку под номером 21, потому что NotAfter (Certificate Expiration Date) не будет больше, чем текущее время. На сегодня всё.

В предыдущей части мы рассмотрели возможность извлечения схемы БД центров сертификации. По схожему принципу можно и делать выборку из неё посредством тех же COM интерейсов. Как я уже говорил, основным интерфейсом для доступа к БД будет ICertView2:

$CaView = New-Object -ComObject CertificateAuthority.View

Подключаемся к конкретному серверу CA при помощи метода OpenConnection:

$CaView.OpenConnection("dc1\contoso ca")

Для начала мы рассмотрим простой пример, выберем все строки из БД, но с заранее определёнными столбцами. Это нужно затем, чтобы не отображать ненужную информацию, которая будет мешать и поедать память. Каждая строка в БД максимально может занимать до полумегабайта места в памяти. Выглядит нестрашно, но когда у вас тысяча запросов в БД это уже будет примерно 500 мегабайт. Но тысяча запросов — это очень даже немного. Поэтому давайте для начала посмотрим все запросы вот в каком виде: номер запроса, имя реквестора (пользователя, от лица которого был подан запрос в CA), имя, на которое выдан сертификат, серийный номер сертификата и срок действия сертификата. Схемы БД для всех существующих Windows CA можно получить при помощи скрипта в предыдущей части или найти и в оффлайне: [MS-CSRA] Product Behavior, note 5.

Необходимые столбцы добавляются при помощи методов SetResultColumnCount, GetColumnIndex и SetResultColumn:

$CaView.SetResultColumnCount(6)
$Column0 = $CaView.GetColumnIndex($false,"RequestID")
$CaView.SetResultColumn($Column0)
$Column1 = $CaView.GetColumnIndex($false,"RequesterName")
$CaView.SetResultColumn($Column1)
<...>

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

$properties = "RequestID","RequesterName","CommonName","NotBefore","NotAfter","SerialNumber"
$CaView.SetResultColumnCount($properties.Count)
$properties | %{$CAView.SetResultColumn($CAView.GetColumnIndex($False, $_))}

Если попытаетесь добавить столбцов больше, чем указано в методе SetResultColumnCount, получите ошибку. Поэтому сначала подсчитывайте количество требуемых столбцов и только после этого их добавлять. В принципе, теперь всё, можно начинать опрашивать БД. Для этого открываем её методом OpenView:

$Row = $CaView.OpenView()

Этот метод вернёт указатель на объект интерфейса IEnumCERTVIEWROW и начинаются шахматы (перевод шагового искателя в исходную позицию). Дальше уже нужно запускать цикл Do…Until или While…Do. Я предпочитаю второй вариант по той простой причине, что если если БД пустая (при указании дополнительных фильтров и ничего под него не подпало) первый же вызов метода Next вернёт –1. Поэтому нужно либо сразу отслеживать значение индекса или использовать такой тип цикла, где значение индекса проверяется перед запуском первой итерации цикла, как While…Do. И вот как это выглядит:

Внимание: если в БД CA находится большое количество запросов, вы получите огромную кучу объектов на выходе, потому что будут возвращены все запросы из секций Issued Certificates, Revoked Certificates и Failed Requests.

# начинаем шахматы и шагать шаговым искателем по строчкам
while ($Row.Next() -ne -1) {
    # создаём пустой объект, который будет являться нашим выходным объектом
    $cert = New-Object psobject
    # переставляем шаговый искатель с вертикального в горизонтальное направление
    $Column = $Row.EnumCertViewColumn()
    # шагаем последовательно по столбцам, которые мы определили вначале скрипта
    while ($Column.Next() -ne -1) {
        # извлекаем название текущего столбца
        $current = $Column.GetName()
        # добавляем свойство к нашему выходному объекту с именем равным имени столбца
        # и добавляем соответствующее значение столбца текущей строчки
        $Cert | Add-Member -MemberType NoteProperty $($Column.GetDisplayName()) -Value $($Column.GetValue(1)) -Force
    }
    # выкидываем объект на выход
    $Cert
    # сбрасываем положение горизонтального шагового искателя
    $Column.Reset()
}
# сбрасываем положение вертикального шагового искателя
$Row.Reset()

И вот как выглядит примерный вывод:

Issued Request ID           : 1
Requester Name              : CONTOSO\DC1$
Issued Common Name          : Contoso CA
Certificate Effective Date  : 2009.02.15. 14:31:15
Certificate Expiration Date : 2014.02.15. 14:40:11
Serial Number               : 5dd87e4cffe3b3bc43f608eb57c767f7

Issued Request ID           : 2
Requester Name              : CONTOSO\DC1$
Issued Common Name          : DC1.contoso.com
Certificate Effective Date  : 2009.02.15. 14:51:45
Certificate Expiration Date : 2010.02.15. 14:51:45
Serial Number               : 15109536000000000002

Issued Request ID           : 3
Requester Name              : CONTOSO\administrator
Issued Common Name          : Users
                              Administrator
Certificate Effective Date  : 2009.02.15. 15:04:02
Certificate Expiration Date : 2011.02.15. 15:04:02
Serial Number               : 151bd2c9000000000003

<...>

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

Иногда бывает очень полезным найти какой-нибудь запрос в БД CA. Можно, конечно же, сделать это через certutil, но это не всегда удобно, потому что если под конкретный запрос подпадает несколько строк из БД, нужно уже вручную как-то парсить вывод, чтобы найти то, что нужно. Поэтому я решил рассказать, как наваять подобный костыль (только религиозно-православный) родными средствами PowerShell и необходимыми COM интерфейсами. Для работы с БД сервера CA нам понадобится лишь один интерфейс: ICertView2.

Каждая БД состоит из столбцов и рядов. То же самое относится и к БД CA (которая, кстати говоря, является стандартной встроенной Jet БД, что позволяет с ней работать стандартными тулзами типа eseutil). Ряды представляют собой набор свойств каждого запроса и для каждого запроса выделяется ровно один ряд. Столбцы представляют собой сами свойства. Каждому столбцу соответствует одно свойство. CA использует БД с заранее созданной (и неизменяемой) схемой, т.е. набором поддерживаемых столбцов. Давайте попробуем вывести схему БД. Для начала создадим объект ICertView2:

$CaView = New-Object -ComObject CertificateAuthority.View

Далее нужно подключиться к какому-то CA серверу при помощи метода OpenConnection. В качестве аргумента следует указать конфигурационную строку CA сервера вида CAComputerName\CAName.

$CaView.OpenConnection("dc1\Contoso CA")

Если ошибок не высыпало, значит, подключение произошло удачно. Теперь мы хотим посмотреть схему и это можно сделать при помощи метода EnumCertViewColumn. В качестве аргумента указываем CVRC_COLUMN_SCHEMA (которое имеет значение 0).

$CaView.EnumCertViewColumn(0)

В результате мы молучим другой объект интерфейса IEnumCERTVIEWCOLUMN. А теперь методом Next будем шагать по каждому столбцу и собирать сведения о нём, как: имя столбца, локализованное имя, тип данных и максимальный размер данных для этого столбца при помощи набора методов, описанных на страничке ICertView2. Вот как это выглядит:

PS C:\> $CaView = New-Object -ComObject CertificateAuthority.View
PS C:\> $CA = "dc1\Contoso CA"
PS C:\> $CaView.OpenConnection($CA)
PS C:\> $Columns = $CaView.EnumCertViewColumn(0)
PS C:\> $Columns.Next()
0
PS C:\> $Columns.GetName()
Request.RequestID
PS C:\> $Columns.GetDisplayName()
Request ID
PS C:\> $Columns.GetType()
1
PS C:\> $Columns.GetMaxLength()
4
PS C:\> $Columns.Next()
1
PS C:\> $Columns.GetName()
Request.RawRequest
PS C:\> $Columns.GetDisplayName()
Binary Request
PS C:\> $Columns.GetType()
3
PS C:\> $Columns.GetMaxLength()
65536
PS C:\>

После метода Next мы видим текущий индекс столбца. Что касается вывода метода GetType(), то вместо типа данных мы видим только циферки. Это нормальное поведение для CryptoAPI интерфейсов. Каждому числу соответствует свой тип данных (их поддерживается всего 4) и вот как они расшифровываются:

  • 1 = Long
  • 2 = DateTime
  • 3 = Binary
  • 4 = String

Если всё это затолкать в цикл DO…UNTIL, можно всё это дело автоматизировать. По правде говоря некоторые столбцы могут содержать массив данных и это можно проверить при помощи метода IsIndexed. Давайте теперь напишем окончательный скрипт, который нам по одному клику выдаст всю схему БД:

function Get-CADataBaseSchema {
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$CAConfig
    )
    # создаём объект ICertView
    try {$CaView = New-Object -ComObject CertificateAuthority.View}
    catch {Write-Error "Unable to instantiate ICertView object"; return}
    # открываем подключение к CA
    $CaView.OpenConnection($CAConfig)
    # говорим, что мы хотим посмотреть схему
    $Columns = $CaView.EnumCertViewColumn(0)
    # начинаем итерацию по столбцам БД
    [void]$Columns.Next()
    do {
        # создаём временный объект, чтобы получить красивый вывод и наполняем его данными
        $Column = "" | Select Name, DisplayName, Type, MaxLength
        $Column.Name = $Columns.GetName()
        $Column.DisplayName = $Columns.GetDisplayName()
        $Column.Type = switch ($Columns.GetType()) {
            1 {"Long"}
            2 {"DateTime"}
            3 {"Binary"}
            4 {"String"}
        }
        [string]$Column.MaxLength = $Columns.GetMaxLength()
        if ($Columns.IsIndexed() -eq 1) {$Column.MaxLength += ", Indexed"}
        $Column
    } until ($Columns.Next() -eq -1)
    # закрываем подключение к БД
    $Columns.Reset()
}

А вывод весьма простенький:

PS C:\> Get-CADataBaseSchema "dc1\Contoso CA"

Name                          DisplayName                   Type                          MaxLength
----                          -----------                   ----                          ---------
Request.RequestID             Request ID                    Long                          4, Indexed
Request.RawRequest            Binary Request                Binary                        65536
Request.RawArchivedKey        Archived Key                  Binary                        65536
Request.KeyRecoveryHashes     Key Recovery Agent Hashes     String                        8192
Request.RawOldCertificate     Old Certificate               Binary                        16384
Request.RequestAttributes     Request Attributes            String                        32768
Request.RequestType           Request Type                  Long                          4
Request.RequestFlags          Request Flags                 Long                          4
Request.StatusCode            Request Status Code           Long                          4
Request.Disposition           Request Disposition           Long                          4, Indexed
Request.DispositionMessage    Request Disposition Message   String                        8192
Request.SubmittedWhen         Request Submission Date       DateTime                      8, Indexed
Request.ResolvedWhen          Request Resolution Date       DateTime                      8, Indexed
Request.RevokedWhen           Revocation Date               DateTime                      8
Request.RevokedEffectiveWhen  Effective Revocation Date     DateTime                      8, Indexed
Request.RevokedReason         Revocation Reason             Long                          4
Request.RequesterName         Requester Name                String                        2048, Indexed
Request.CallerName            Caller Name                   String                        2048, Indexed
Request.SignerPolicies        Signer Policies               String                        8192
Request.SignerApplicationP... Signer Application Policies   String                        8192
Request.Officer               Officer                       Long                          4
<...>

В следующей части (или частях, пока ещё не придумал) поговорим о том, как блуждать по самой БД.

Как известно, когда запрос попадает в папку 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.