Feeds:
Записи
Комментарии

В этой заметке, кратко опишу установку роли Remote Desktop Gateway в самой простой конфигурации на Windows Server 2019(17763) с указанием порта отличного от стандартного.

ComputerName

IPAddress

HTTPPort(default) 

UDPPort(default) 

  srv-rdgw.contoso.com

192.168.0.21 

443

3391

1. Установка роли RDS-Gateway

Проверим, что роль не установлена:

PS > Get-WindowsFeature RDS*

Display Name                                            Name                       Install State
------------                                            ----                       -------------
    [ ] Remote Desktop Connection Broker                RDS-Connection-Broker          Available
    [ ] Remote Desktop Gateway                          RDS-Gateway                    Available
    [ ] Remote Desktop Licensing                        RDS-Licensing                  Available
    [ ] Remote Desktop Session Host                     RDS-RD-Server                  Available
    [ ] Remote Desktop Virtualization Host              RDS-Virtualization             Available
    [ ] Remote Desktop Web Access                       RDS-Web-Access                 Available
            [ ] Remote Desktop Licensing Tools          RDS-Licensing-UI               Available
  

Установка:

PS > Install-WindowsFeature RDS-Gateway -IncludeAllSubFeature -IncludeManagementTools

Success Restart Needed Exit Code      Feature Result
------- -------------- ---------      --------------
True    No             Success        {Network Policy and Access Services, Remot...
  

2. Конфигурирование портов GUI

Попробуем задать порт отличный от стандартного с указанием IP адреса. Запускаем оснастку tsgateway.msc , выбираем сервер и нажимаем Properties.

tpprop-01122018

Transport-0122018

Выскакивает ошибка, что установить порт не удалось. В EventLog регистрируются два события, в которых указано, что не удалось изменить стандартные правила в Firewall.

error-01122018

Проверим стандартные правила в Firewall:

PS > Get-NetFirewallRule TSG-HTTPS-Transport-In-TCP,TSG-UDP-Transport-In-UDP | Select Name, Enabled

Name                       Enabled
----                       -------
TSG-HTTPS-Transport-In-TCP   False
TSG-UDP-Transport-In-UDP      True
  

Для HTTP транспорта, оно не включено.

PS > Get-NetFirewallRule TSG-HTTPS-Transport-In-TCP,TSG-UDP-Transport-In-UDP | Get-NetFirewallPortFilter


Protocol      : UDP
LocalPort     : 3391
RemotePort    : Any
IcmpType      : Any
DynamicTarget : Any

Protocol      : TCP
LocalPort     : 443
RemotePort    : Any
IcmpType      : Any
DynamicTarget : Any
  

Новые правила, как в предыдущих версиях Windows Server, автоматически не создались для нестандартных портов.

Посмотрим изменения в IIS:

bin-01122018

Настройки применились, в tsgateway.msc  все без изменений. Проверим порты:

PS > netstat -an | Select-String "4443|4444|3391"

  TCP    0.0.0.0:4443           0.0.0.0:0              LISTENING
  TCP    [::]:4443              [::]:0                 LISTENING
  UDP    0.0.0.0:3391           *:*
  UDP    [::]:3391              *:*
  

Для UDP порт остался прежним.

* Правила Firewall по умолчанию:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Defaults\FirewallPolicy\FirewallRules
  

PS > Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Defaults\FirewallPolicy\FirewallRules\ -Name TSG*Transport*


TSG-HTTPS-Transport-In-TCP : v2.29|Action=Allow|Active=FALSE|Dir=In|Protocol=6|LPort=443|App=System|Name=@FirewallAPI.dll,-39137|Desc=@FirewallAPI.
                             dll,-39138|EmbedCtxt=@FirewallAPI.dll,-39135|
TSG-UDP-Transport-In-UDP   : v2.29|Action=Allow|Active=TRUE|Dir=In|Protocol=17|LPort=3391|App=%systemroot%\system32\svchost.exe|Svc=tsgateway|Name=
                             @FirewallAPI.dll,-39139|Desc=@FirewallAPI.dll,-39140|EmbedCtxt=@FirewallAPI.dll,-39135|
  

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

Часть настроек Remote Desktop Gateway хранится в реестре: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\TerminalServerGateway

Другая часть, хранится в файле rap.xml:

C:\Windows\System32\tsgateway\rap.xml

Проверим, настройки в реестре:

treg-01122018

Изменения не применились, все осталось по умолчанию.

3. Конфигурирование портов скриптом

# Константы
$IPAddress = "192.168.0.21"
$HTTPPort = 4443
$UDPPort = 4444
$hklm = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\TerminalServerGateway\Config\Core"

# Создаем правила
Remove-NetFirewallRule TSG-HTTPS-Transport-In-TCP,TSG-UDP-Transport-In-UDP
New-NetFirewallRule -Name "TSG-HTTPS-Transport-In-TCP" -DisplayName "Remote Desktop Gateway HTTP Listener" -Direction Inbound -LocalPort 4443 -Protocol TCP -Action Allow -Profile Any
New-NetFirewallRule -Name "TSG-UDP-Transport-In-UDP" -DisplayName "Remote Desktop Gateway UDP Listener" -Direction Inbound -LocalPort 4444 -Protocol UDP -Action Allow -Profile Any

# Останавливаем службу
Stop-Service TSGateway

# Изменяем IP и порт для UDP
Set-ItemProperty -Path $hklm -Name HttpIPAddress -Value $IPAddress
Set-ItemProperty -Path $hklm -Name UdpIPAddress -Value $IPAddress
Set-ItemProperty -Path $hklm -Name UdpPort -Value $UDPPort

# Изменяем IP и порт для UDP 
Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServerSettings | 
    Invoke-CimMethod -MethodName SetIPAndPort -Arguments @{TransportType = 2;IPAddress = $IPAddress; Port = $UDPPort}

Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServerSettings | 
    Invoke-CimMethod -MethodName Configure	
	
# Запуск службы
Start-Service TSGateway

# Изменяем IP и порт для TCP
Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServerSettings | 
    Invoke-CimMethod -MethodName SetIPAndPort -Arguments @{TransportType = 1;IPAddress = $IPAddress; Port = $HTTPPort; OverrideExisting = 1}
	
Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServerSettings | 
    Invoke-CimMethod -MethodName Configure
	

# Перезапуск службы
Restart-Service TSGateway

# Изменяем IP и порт для TCP 
Set-ItemProperty -Path $hklm -Name HttpsPort -Value $HTTPPort

Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServerSettings | 
    Invoke-CimMethod -MethodName SetIPAndPort -Arguments @{TransportType = 1;IPAddress = $IPAddress; Port = $HTTPPort; OverrideExisting = 1}
	
Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServerSettings | 
    Invoke-CimMethod -MethodName Configure	

Restart-Service TSGateway

# Создаем self-certificate и привязываем его
$cert = Invoke-CimMethod -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServer -MethodName CreateSelfSignedCertificate -Arguments @{SubjectName = "SRV-RDGW.contoso.com"}
$CertHash = $cert.CertHash
Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServerSettings | 
    Invoke-CimMethod -MethodName SetCertificate -Arguments @{CertHash = $CertHash}

Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServerSettings | 
    Invoke-CimMethod -MethodName Configure
	
Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServerSettings | 
    Invoke-CimMethod -MethodName SetCertificateACL -Arguments @{CertHash = $CertHash}

Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServerSettings | 
    Invoke-CimMethod -MethodName RefreshCertContext -Arguments @{CertHash = $CertHash}

			
Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGatewayServerSettings | 
    Invoke-CimMethod -MethodName Configure

Restart-Service TSGateway
  

После применения настроек:

tnew-01122018

PS > netstat -an | Select-String "4443|4444|3391"

  TCP    0.0.0.0:4443           0.0.0.0:0              LISTENING
  TCP    [::]:4443              [::]:0                 LISTENING
  UDP    192.168.0.21:4444      *:*
  

IIS:

iis-01122018

В рамках данной статьи, опишу процесс автоматической установки и настройки PowerShell Core и OpenSSH под Windows Server 2019 Build 17763. В качестве клиентской ОС – будет выступать CentOS Linux release 7.5.1804.

Name

Role

OS

srv-contoso-dc01

ADDS, DNS

Windows Server 2019 Build 17763

cl-centos-01

Client

CentOS Linux release 7.5.1804

1. Установка PowerShell Core на CentOS 7.5

Процесс довольно прост и хорошо описан по ссылке — Installing PowerShell Core on Linux

# Register the Microsoft RedHat repository
curl https://packages.microsoft.com/config/rhel/7/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo

# Install PowerShell
sudo yum install -y powershell

# Start PowerShell
pwsh
  

Результат выполнения – PSVersion 6.1.0 :

psvercentos

2. Установка PowerShell Core и Win32-OpenSSH на Windows Server 2019

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

   1. Скачивание дистрибутивов с GitHub

       https://github.com/PowerShell/PowerShell/releases/latest

       https://github.com/PowerShell/Win32-OpenSSH/releases/latest

   2. Установка PowerShell Core

       — Включение Windows Remoting

       — Создание SymbolicLink

There is a bug in OpenSSH for Windows that prevents spaces from working in subsystem executable paths. For more information, see this GitHub issue.

One solution is to create a symlink to the Powershell installation directory that doesn’t have spaces.

   3. Установка Win32-OpenSSH

       — Создаем новый конфиг $env:ProgramData\ssh\sshd_config

       — Параметры конфига sshd_config

AuthorizedKeysFile      .ssh/authorized_keys

PasswordAuthentication yes

PubkeyAuthentication yes

Subsystem       sftp    sftp-server.exe

Subsystem       powershell $SymbolicLinkPath\pwsh.exe -sshs -NoLogo –NoProfile

   4. Удаление файлов с дистрибутивами

Текущие ограничения, можно посмотреть на странице — remoting-over-ssh:

Known Issues:

  1. You can currently establish a connection either interactively with user name and password or via key authentication. PSCredential is not yet supported.
  2. SSH connection attempt errors are not currently surfaced. There is a pull request with a fix for this. But for now you need to use Ctrl+C to abort the connection attempt and wait for the one minute session close timeout. To avoid connection errors make sure HostName and UserName names are correct and that the sshd service is running on the target machine.
  3. Endpoint configuration and JEA is not yet supported.
  4. Remote debugging from Linux to Windows does not work. However, remote debugging from Windows to Linux does work.
  5. Fan out to multiple machines not yet supported.
  6. sudo command does not work in remote session to Linux machine.

Запуск скриптаpwsh_openssh_install_win64.ps1.txt:

& C:\pwsh_openssh_install_win64.ps1 -Verbose
  

Статус выполнения:

ssh-scr

Переходим на Linux и подключаемся к нашему DC:

w-ssh-01

w-ssh-02

w-ssh-03

Теперь можно использовать модуль Active Directory, DnsServer и многие другие в PowerShell Core.

Полезные ссылки:

https://docs.microsoft.com/en-us/powershell/scripting/core-powershell/ssh-remoting-in-powershell-core?view=powershell-6

https://docs.microsoft.com/en-us/powershell/scripting/core-powershell/wsman-remoting-in-powershell-core?view=powershell-6

https://github.com/PowerShell/Win32-OpenSSH/issues/784

https://github.com/PowerShell/Win32-OpenSSH/wiki/Install-Win32-OpenSSH

https://github.com/PowerShell/Win32-OpenSSH/wiki/How-to-retrieve-links-to-latest-packages

PS. Скрипт автоматической установки — pwsh_openssh_install_win64.ps1.txt

Оператор -replace

Оператор -replace в PowerShell — является упрощенной реализацией метода Replace класса System.Text.RegularExpressions.Regex –> альяс в [regex]::Replace.

Реализация оператора –replace в PowerShell Core методом internal static object ReplaceOperator.

Синтаксис:

PowerShell Desktop

          input –[ci]replace pattern, [replacement]

PowerShell Core

          input –[ci]replace pattern, [replacement]

          input –replace pattern, [evaluator]

  [] – необязательные аргументы

   input – входная строка или массив строк

   pattern – шаблон регулярного выражения

   replacement – строка замены

   evaluator — пользовательский метод, анализирующий каждое совпадение и возвращающий либо исходную строку с совпадениями, либо строку замены.

 

Большинство –op операторов, представлены в двух версиях:

-creplace : с учетом регистра (case sensitive (-cop)) , указывается опция RegexOptions.None

-ireplace = replace : без учета регистра (case-insensitive (-iop)) , указывается опция RegexOptions.IgnoreCase

Пару условных правил:

  1. -replace не изменяет первоначальную строку, а возвращает новый объект строки в измененном варианте
  2. при несовпадении шаблона, строка возвращается как есть
  3. при отсутствии шаблона используется ""

Примеры:

# input -[ci]replace pattern

PS > "Hello" -replace "ll"
Heo

PS > "Hello" -ireplace "h"
ello

PS > "Hello" -creplace "h"
Hello

# input –[ci]replace pattern, [replacement]

PS > "aabc" -replace "a","b"
bbbc

PS > "Abcd" -ireplace "a","B"
Bbcd

PS > "Abcd" -creplace "[a-z]","B"
ABBB
  

В PowerShell Core добавили  параметр evaluator, который может принимать следующие значения:

  1. ScriptBlock
  2. Объект класса [System.Text.RegularExpressions.MatchEvaluator]
  3. Пользовательский класс с статическим методом

ScriptBlock, каждое совпадение по шаблону сохраняется в переменной $_ или $psitem.

$_.GetType().FullName — System.Text.RegularExpressions.Match

PS > "A1B2C3" -replace "\d", {[int]$_[0].Value*2}
A2B4C6

PS > "A1B2C3" -replace "\d", {$psitem[0].Value*3}
A111B222C333


PS > $_[0]


Groups   : {0}
Success  : True
Name     : 0
Captures : {0}
Index    : 5
Length   : 1
Value    : 3

PS > $_[0].Value
3
  



MatchEvaluator, каждое совпадение по шаблону сохраняется в переменной $args или param($var).



PS > $matchEvaluator = {[int]$args[0].Value*2} -as [System.Text.RegularExpressions.MatchEvaluator]
PS > "A1B2C3" -replace "\d", $matchEvaluator
A2B4C6

PS > $matchEvaluator = [System.Text.RegularExpressions.MatchEvaluator]{param($m) [int]$m[0].Value*3}
PS > "A1B2C3" -replace "\d", $matchEvaluator
A3B6C9
  



Пользовательский класс с статическим методом, каждое совпадение по шаблону сохраняется в переменной MethodName([ObjectClass]$var).



PS >
class matchEvaluator {
    static [string] Replace([System.Text.RegularExpressions.Match]$m) {
	return [int]$m[0].Value*3
    }
}

"A1B2C3" -replace "\d", [matchEvaluator]::Replace

A3B6C9
  

В PowerShell Desktop evaluator можно использовать через [regex]::Replace.

PS >  [regex]::Replace("A1B2C3","\d",{[int]$args[0].Value*2})
A2B4C6
PS >  [regex]::Replace("A1B2C3","\d",{param($m) [int]$m[0].Value*3})
A3B6C9
  

Для массива строк мы можем использовать аналогичные действия:

PS > "1abc","2qwe","3rty" -replace "\d"
abc
qwe
rty

  

PS > "1abc","2qwe","3rty" -replace "\d","X"

Xabc

Xqwe

Xrty

PS > "1abc","2qwe","3rty" -replace "\d",{[int]$_[0].Value*2}
2abc
4qwe
6rty
  

Подстановки в регулярных выражениях подробно описано на docs.microsoft.com,я лишь приведу примеры использования.

* Для экранирования символа подстановки $ используйте ` (backtick) – "`$" если используются двойные кавычки

   ‘$’ – не требуется экранирования

   "`$" – экранирование

   ("Abc`$1" + ‘$1’ + "Xyz") – комбинирование



# $number The text matched by group number number.
# $0 - весь шаблон
# $1 - первая группа
# $2 - вторая группа 

PS > "111-2222" -replace "(\d{3})-(\d{4})",'($1) : $2'
(111) : 2222

PS > "111-2222" -replace "(\d{3})-(\d{4})","(`$1) : `$2"
(111) : 2222

PS > "111-2222" -replace "(\d{3})-(\d{4})","'`$`$1 - `$1' : `$`$2 - '`$2'"
'$1 - 111' : $2'2222'

PS > "String" -replace "(.+)ing",("Abc" + '$1' + "Xyz")
AbcStrXyz

# ${name} The text matched by group named name.

PS > "Dog name" -replace "^(?<dog>.{3}).+",'${dog} pop'
Dog pop

# $$ A literal $.
  

PS > "1234" -replace "\d",'$$'
$$$$

#$& A copy of the entire match.
  

PS > "Moscow" -replace "^.*$",'Capital: $&'
Capital: Moscow

#$` The text of the input string that precedes the match.
  

PS > "PreText" -replace "Text$",'$`'
PrePre

#$' The text of the input string that follows the match.
  

PS > "PreText" -replace "Pre",'$'''
TextText

#$+ The last group captured.
  

PS > "PreText" -replace "(.+)Text",'$+Now'
PreNow

#$_ The entire input string.
  

PS > "String" -replace "(.*)ing",'Entire String: $_'
Entire String: String
  

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

ld26042018

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

Поэтому предварительно :

  • Права эквивалентные локальному администратору на удаленном ПК
  • Настроен PowerShell Remoting
  • Пользователь в консольном сеансе

Часть информации можно получить из реестра при определенных условиях:

HKCU:\Network\ — Содержит диски подключенные с указанием флага PERSISTEN

HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\##* — Может содержать и актуальную,и неактуальную информацию

Т.е. актуальную информацию из реестра получить будет непросто,но возможно, кому-то помогут данные ключи.

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

Registry::HKEY_USERS\SID\

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

  • Invoke-Command для выполнения скрипта на удаленном ПК
  • Schedule.Service для отслеживания результата выполнения задачи
  • schtasks.exe простой способ создать задачу

Function Get-MappedLogicalDisk {
	[CmdletBinding()]
	Param (
		[Parameter(ParameterSetName='ComputerName', Position=0)]
		[ValidateNotNullOrEmpty()]
		[String[]]$ComputerName,
		
		[Parameter(ParameterSetName='Session', Position=0)]
		[ValidateNotNullOrEmpty()]
		[System.Management.Automation.Runspaces.PSSession[]]
		$Session,
		
		[Parameter(ParameterSetName='ComputerName')]
		[System.Management.Automation.PSCredential]
		[System.Management.Automation.CredentialAttribute()]
		$Credential,
		
		[Parameter(ParameterSetName='ComputerName')]
		[ValidateRange(1, 65535)]
		[Int]$Port,
		
		[Parameter(ParameterSetName='ComputerName')]
		[Switch]$UseSSL,
		
		[Parameter(ParameterSetName='ComputerName')]
		[String]$ConfigurationName,
		
		[Parameter(ParameterSetName='ComputerName')]
		[System.Management.Automation.Runspaces.AuthenticationMechanism]
		$Authentication,
		
		[Parameter(ParameterSetName='ComputerName')]
		[String]$CertificateThumbprint
	)

	$script = {
		# Текущий пользователь в системе выполнивший вход через консольный сеанс
		$user = (Get-WmiObject -Class Win32_ComputerSystem).UserName
		# Название задачи
		$tn = "GetMappedLogicalDisk"
		# Папка для временного файла
		$logdir = "C:\LogMAP"
			
		# Команда для получения сетевых дисков в контексте пользователя
		# Возможно ограничение в 260 символов 
		$arg = '&{{New-Item -Path {0} -Type Directory -Force;Get-WmiObject Win32_MappedLogicalDisk | Export-Clixml {0}\$env:UserName.xml}}' -f $logdir
		$cmd = 'powershell.exe -WindowStyle Hidden -NoProfile -NoLogo -Command {0}' -f $arg
					
		if($user) {
			# Создаем ComObject для работы с Task Scheduler
			$sch = New-Object -ComObject Schedule.Service
			$sch.Connect()
			# Корневая директория для заданий
			$taskdir = $sch.GetFolder("\")
			
			# Удаляем прежние данные
			Remove-Item -Path $logdir -Force -Recurse -ErrorAction SilentlyContinue
				
			# Создаем задачу в контексте пользователя
			$null = schtasks.exe /create /TN $tn /ST 00:00 /RU $user /SC ONCE /TR $cmd /f 2>&1
			# Запускаем задачу
			$null = $taskdir.GetTask($tn).Run($null)
				
			# Проверим завершение задачи независимо от локализации ОС
			$maxcount = 0
			While($taskdir.GetTask($tn).State -ne 3 -and $maxcount -lt 60) {
				$maxcount++
				Start-Sleep -Sec 1
			}
				
			# Получаем результат
			Import-CliXML $logdir\*.xml | Add-Member -Type NoteProperty -Name User -Value $user -PassThru
				
			# Удаляем временную задачу и файлы
			Remove-Item -Path $logdir -Force -Recurse -ErrorAction SilentlyContinue
			$null = $taskdir.DeleteTask($tn,0)
		} else {
			"В системе нет пользователей выполнивших вход!"
		}
	}
		
	Invoke-Command @PSBoundParameters -ScriptBlock $script
}
  

Примерный вывод:

PS > Get-MappedLogicalDisk -ComputerName con-desk-01 -Credential "contoso\admin" | Format-Table Name,ProviderName,FileSystem,FreeSpace,Size -AutoSize

  User        Name ProviderName      FileSystem   FreeSpace     Size
  ----        ---- ------------      ----------   ---------     ----
  CON\ivanov  Y:   \\fs01\Документы  NTFS         775772479488  1000198893568
  CON\ivanov  Z:   \\fs02\Фото       NTFS         775772479488  1000198893568
  

Скрипт — GetMappedLogicalDiskPowerShellRemoting.txt

Сегодня анонсировали возможность получения бесплатного wildcard-сертификата от Let’s Encrypt сроком на 3 месяца — ACME v2 and Wildcard Certificate Support is Live . Технические подробности можно узнать – ACME v2 Production Environment & Wildcards.

  • Требуется повторная проверка домена для ACME v2
  • Учетные записи для V1 или V2 staging environment(тестовых сред) не будут работать в рабочей среде
  • ACME v2  поддерживает не более 300 запрос в течение 3 часов
  • Проверка осуществляется только с помощью DNS-01 challenge

В прошлый раз использовали модуль ACMESharp, на данный момент он не поддерживает ACME v2, но я думаю в скором времени, все будет поддерживаться. В библиотеке certes реализована поддержка  ACME v2, ее и  будем использовать.

Для тренировки получения сертификатов указывайте [Certes.Acme.WellKnownServers]::LetsEncryptStagingV2 , в этом случае издатель будет   — Fake LE Intermediate and Root X1. Для рабочей среды – [Certes.Acme.WellKnownServers]::LetsEncryptV2.

!! Создания записи _acme-challenge.contoso.com для DNS ,в нашем примере — Windows DNS Server !!

 

 

# Path - где храним библиотеки            
# Mail - почта администратора            
# Domain - домен для которого получаем сертификат            
# PfxPassword - пароль для Pfx            
$Path = "C:\certes"            
$Mail = "mailto:admin@contoso.ru"            
$Domain = "*.contoso.ru"            
$PfxFriendlyName = "CONTOSO_{0}" -f (Get-Date).AddMonths(3).ToString("MMyy")            
$PfxPath = Join-Path $Path "wildcard_cert.pfx"            
$PfxPassword = "12345678"            
            
# Ссылки для скачивания nupkg            
$Urls = (            
 "https://www.nuget.org/api/v2/package/Certes",            
 "https://www.nuget.org/api/v2/package/BouncyCastle.Crypto.dll",            
 "https://www.nuget.org/api/v2/package/Newtonsoft.Json"            
)            
            
# Указываем библиотеки для проверки            
$Dlls = (            
 "$Path\lib\net45\Certes.dll",            
 "$Path\lib\BouncyCastle.Crypto.dll",            
 "$Path\lib\net45\Newtonsoft.Json.dll"            
)            
            
            
# Создаем папку            
if( -not (Test-Path $path) ) {            
 New-Item -Type Directory -Path $path | Out-Null            
}            
            
Test-Path $Dlls | Where-Object {-not $_} | Foreach-Object {             
 $Urls | Foreach-Object {            
  # Скачиваем nupkg            
  $r = Invoke-WebRequest $_ -UseBasicParsing            
  $filename = Join-Path $path $r.BaseResponse.ResponseUri.Segments[-1]            
  [System.IO.File]::WriteAllBytes($filename,$r.Content)            
            
  # Распаковываем nupkg            
  Expand-Archive $filename  -OutputPath $path -Force            
 }            
}            
            
# Удалим лишнее            
Get-ChildItem $path | Where {$_.Name -ne "lib"} | Remove-Item -Force -Recurse            
            
# Подгрузи библиотеки            
$Dlls | Foreach-Object {            
 [System.Reflection.Assembly]::LoadFile($_) | Out-Null            
}            
            
            
# Создадим учетную запись            
# Для тестового использования LetsEncryptStagingV2            
$context = [Certes.AcmeContext]::new([Certes.Acme.WellKnownServers]::LetsEncryptV2)            
$context           
$account = $context.NewAccount([System.Collections.Generic.List[string]]$Mail, $true)            
$account.Result            
            
# Формируем запрос на получение wildcard            
$order = $context.NewOrder([Collections.Generic.List[string]]$Domain)            
$authz = $order.Result.Authorizations()            
$dnsChallenge = $authz.Result.Resource().Result.Challenges            
            
# Содержит token для DNS записи _acme-challenge.contoso.com            
$dnsTxt = [Certes.ISignatureKeyExtensions]::DnsTxt($context.AccountKey,$dnsChallenge.Token)            
            
# Создаем ресурсные записи             
# Windows DNS Server            
$DomainName = $Domain.Replace("*.","")            
$Name = "_acme-challenge"            
$record = Get-DnsServerResourceRecord -ZoneName $DomainName -Name $Name -RRType Txt            
            
if($record)            
{            
 $newrecord = $record.Clone()            
 $newrecord.RecordData.DescriptiveText = $_.RecordValue            
 Set-DnsServerResourceRecord -ZoneName $DomainName -NewInputObject $newrecord -OldInputObject $record            
}            
else            
{            
 Add-DnsServerResourceRecord -ZoneName $DomainName -Txt -Name $Name -DescriptiveText $dnsTxt            
}            
            
            
# Получаем сертификат            
if($dnsTxt) {            
 # Отправляем запрос на проверку DNS записи            
 $validatetatus = $authz.Result.Challenges().Result.Validate()            
 $validatetatus.Result            
             
 # Finalize            
 $csr = [Certes.Pkcs.CertificationRequestBuilder]::new()            
 $csr.AddName("CN=$Domain")            
 $order.Result.Finalize($csr.Generate())            
             
 # Скачиваем цепочку сертификатов            
 $certChain = $order.Result.Download()            
 $cert = $certChain.Result            
              
 # Экспорт в Pfx                        
 $pfx = [Certes.CertificateChainExtensions]::ToPfx($cert,$csr.Key)            
 $pfxcert = $pfx.Build($PfxFriendlyName, $PfxPassword)            
 [System.IO.File]::WriteAllBytes($PfxPath,$pfxcert)            
}            
            
# Отзыв сертификата            
# $context.RevokeCertificate($cert.Certificate.ToDer(), [Certes.Acme.Resource.RevocationReason]::KeyCompromise,$null)

context - 140318

account - 140318

 

order - 140318

order_answ - 140318

dnschall - 140318

dnschall_ans -140318

cert - 140318

 

PS. letsencrypt_wildcard_certes.txt

Для перемещения формы, у которой свойство FormBorderStyle=None, воспользуемся советом из топика – "Перемещение формы без границ — C#" за авторством mak326428.

#Requires -PSEdition Desktop            
            
using assembly System.Windows.Forms            
using namespace System.Windows.Forms            
            
$form = [Form]@{            
 FormBorderStyle = "None"            
}            
            
$form.Add_MouseDown({            
 $form.Capture = $false            
 $m = [Message]::Create($form.Handle, 0xa1, (New-Object IntPtr(2)), [IntPtr]::Zero)            
 $form.WindowTarget.DefWndProc([ref]$m)            
})            
            
$form.ShowDialog()
moveform

Скрипт — MoveFormBorderStyleNone.ps1

В наборе  Windows Server 2003 Resource Kit Tools , есть утилита tsctst.exe — Terminal Server Client License Dump , для просмотра клиентских лицензий сервера терминалов. Основное ограничение на системах x64, что tsctst.exe –  читает раздел реестра — HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\MSLicensing\Store , где отсутствует данная информация.

Если хочется воспользоваться данной утилитой, то можно скопировать/импортировать данные из HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\MSLicensing\Store и перенести в раздел :  HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSLicensing\Store и утилита вновь будет работать.

Нас будет интересовать ключ реестра LicenseXXX\ClientLicense(REG_BINARY), где содержится информация о сертификатах.

Для получения данных о сертификате , воспользуемся(для примера, приведу  два способа):

1) System.Security.Cryptography.Pkcs.SignedCms

Add-Type -AssemblyName System.Security            
$license = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\MSLicensing\Store\LICENSE000" -Name "ClientLicense"            
$cms = New-Object System.Security.Cryptography.Pkcs.SignedCms            
$cms.Decode($license.ClientLicense)            
$cms.Certificates             
            
PS > $cms.Certificates

Thumbprint                                Subject
----------                                -------
785F491C579B6421FE155EFB6E6482F1E44FC266  L=CON + CN=SBTR
B50C54A586ADC8FA249639B7A06E3CE4B2C24070  L=SYSTEM + CN=CL-01 + SERIALNUMBER="1BcKee1dW/BhjacbI9tTm0hPCQ

2) System.Security.Cryptography.X509Certificates.X509Certificate2Collection

$certs = [System.Security.Cryptography.X509Certificates.X509Certificate2Collection]::new()            
$certs.Import($license.ClientLicense)            
$certs            
            
PS > $cms.Certificates

Thumbprint                                Subject
----------                                -------
785F491C579B6421FE155EFB6E6482F1E44FC266  L=CON + CN=SBTR
B50C54A586ADC8FA249639B7A06E3CE4B2C24070  L=SYSTEM + CN=CL-01 + SERIALNUMBER="1BcKee1dW/BhjacbI9tTm0hPCQ

Можно сохранить данные в файл и посмотреть, через оснастку — certmgr.msc:

$license.ClientLicense | Set-Content certs.p7b -Encoding Byte

Утилита tsctst.exe читает следующие OID:

Microsoft Hydra……………………………1.3.6.1.4.1.311.18

     License Info root

        szOID_PKIX_LICENSE_INFO                 1.3.6.1.4.1.311.18.1

     Manufacturer value

        szOID_PKIX_MANUFACTURER                 1.3.6.1.4.1.311.18.2

     Manufacturer Specfic Data

        szOID_PKIX_MANUFACTURER_MS_SPECIFIC     1.3.6.1.4.1.311.18.3

     OID for Certificate Version Stamp

        szOID_PKIX_HYDRA_CERT_VERSION           1.3.6.1.4.1.311.18.4

     OID for License Server to identify licensed product.

        szOID_PKIX_LICENSED_PRODUCT_INFO        1.3.6.1.4.1.311.18.5

     OID for License Server specific info.

        szOID_PKIX_MS_LICENSE_SERVER_INFO       1.3.6.1.4.1.311.18.6

     Extension OID reserved for product policy module — only one is allowed.

        szOID_PKIS_PRODUCT_SPECIFIC_OID         1.3.6.1.4.1.311.18.7

        szOID_PKIS_TLSERVER_SPK_OID             1.3.6.1.4.1.311.18.8

Вывод утилиты:

PS > tsctst.exe /A

*** License # 1 ***

TS Certificate Version — 0x00050001

Licensed Product

        HWID — 0x00000002, 0x7d4eb2c0, 0x37ffa910, 0x75eb542b, 0x87707af6

        Client Platform ID — 0x000000ff

        Company Name — Microsoft Corporation

Issuer — SBTR

Scope — COM

Issued to machine — CL-01

Issued to user — SYSTEM

        TS Locale ID — 0x00000419

        License ID — A02-5.02-S

Licensed Product Version 0005.0002, Flag 0x80d48000

Temporary       RTM

Valid from — 1d30f66 8ff2a100 Mon Aug 07 13:18:50 2017

Expires on — 1d3561f 79202100 Sun Nov 05 13:18:50 2017

Поля:

Issuer,Scope,Issued to machine,Issued to user -  Subject,Issuer

TS Certificate Version — 1.3.6.1.4.1.311.18.4(szOID_PKIX_HYDRA_CERT_VERSION)

Licensed Product — 1.3.6.1.4.1.311.18.2(szOID_PKIX_MANUFACTURER),

                 1.3.6.1.4.1.311.18.6 (szOID_PKIX_MS_LICENSE_SERVER_INFO)

TS Locale ID  -   1.3.6.1.4.1.311.18.5( szOID_PKIX_LICENSED_PRODUCT_INFO)

License ID — 1.3.6.1.4.1.311.18.5( szOID_PKIX_LICENSED_PRODUCT_INFO)

Licensed Product Version — 1.3.6.1.4.1.311.18.5( szOID_PKIX_LICENSED_PRODUCT_INFO)

Valid from – NotBefore

Expires on – NotAfter

Function Get-TSCtst {            
 Param(            
  $Path = "HKLM:\SOFTWARE\Microsoft\MSLicensing\Store"            
 )            
             
 $Certs = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection            
 $License = Get-ChildItem $Path | Get-ItemProperty -Name ClientLicense             
             
 $LicenseType = @{            
  "A02-5.00-EX" = 'Windows 2000 TS CAL from the "Built-in" pool.'             
  "A02-5.00-S"  = "Windows 2000 TS temporary or permanent CAL."             
  "A02-5.02-S"  = "Windows server 2003 TS temporary or permanent CAL."             
  "A02-6.00-S"  = "Windows server 2008 TS/2008 R2 RDS temporary or permanent CAL."             
 }            
             
 if(-not $License)             
 {            
  "Can't decode license"            
  break            
 }            
             
 foreach($l in $License)             
 {            
  $Certs.Import($l.ClientLicense)            
 }            
             
            
            
 $Certs = $Certs.Where{$_.Subject -match "SERIALNUMBER"}            
 $Number = 1            
             
 foreach($Cert in $Certs)            
 {            
  #Info            
  [byte[]]$info = $Cert.Extensions | Where{$_.Oid.Value -eq '1.3.6.1.4.1.311.18.5'} | Foreach {$_.RawData}             
  #TSVer            
  [byte[]]$btsver = $Cert.Extensions | Where{$_.Oid.Value -eq '1.3.6.1.4.1.311.18.4'} | Foreach {$_.RawData}            
  [Array]::Reverse($btsver)            
  $TSVer = "0x$([BitConverter]::ToString($btsver).replace('-',''))"            
  #Compnay            
  $bcompany = $Cert.Extensions | Where {$_.Oid.Value -eq '1.3.6.1.4.1.311.18.2'} | Foreach {$_.RawData}            
  $Company = [Text.Encoding]::Unicode.GetString($bcompany)            
  #Product            
  $CId = "0x$([BitConverter]::ToString($info[5..8]).Replace('-','').ToLower())"            
  #Issuer,Scope            
  $Scope,$Issuer = $Cert.Issuer -split "\s\+\s" | Foreach {$_.split("=")[1]}            
  #User,Machine Issuer            
  $UIssuer,$MIssuer = $Cert.Subject -split "\s\+\s" -match "^(L|CN)" | Foreach {$_.split("=")[1]}            
  #Locale            
  $bid = $info[12..13]            
  [Array]::Reverse($bid)            
  $TSId = "0x$([BitConverter]::ToString($bid).Replace('-','').PadLeft(8,'0'))"            
  #License            
  [string]$LicStr = [Text.Encoding]::Unicode.GetString($info) -split "\0" -match "A02-"            
  #License Type            
  $LicType = $LicenseType[$LicStr]            
  #Licensed Product Version            
  $LPV = "{0}.{1}, Flag 0x{2}" -f [BitConverter]::ToString($info,58,1).PadLeft(4,'0'),            
   [BitConverter]::ToString($info,60,1).PadLeft(4,'0').ToLower(),            
   [BitConverter]::ToString($info,63,4).Replace('-','').ToLower()            
  #HWID            
  $RegHWID    = "HKLM:\SOFTWARE\Microsoft\MSLicensing\HardwareID"            
  $ClientHWID = (Get-ItemProperty $RegHWID).ClientHWID            
  $HexClientHWID = for($i=0;$i -le $ClientHWID.Count -1 ; $i+=4) {            
   $temp = $ClientHWID[$i..($i+3)]            
   [Array]::Reverse($temp)            
   "0x$([BitConverter]::ToString($temp).Replace('-','').ToLower())"            
  }            
  $HexClientHWID = $HexClientHWID -join ', '            
              
  [PSCustomObject]@{            
   "ID"        = $Number            
   "TS Certificate Version"   = $TSVer            
   "HWID"         = $HexClientHWID            
   "Client Platform ID"       = $CId            
   "Company Name"             = $Company            
   "Issuer"        = $Issuer            
   "Scope"         = $Scope            
   "Issued to machine"     = $MIssuer            
   "Issued to user"      = $UIssuer            
   "TS Locale ID"       = $TSId            
   "License ID"       = $LicStr            
   "License Type"      = $LicType            
   "Licensed Product Version" = $LPV            
   "Valid from"       = $Cert.NotBefore            
   "Expires on"       = $Cert.NotAfter            
  }            
              
  $Number++            
 }            
}            
            

ID                       : 1
TS Certificate Version   : 0x00050001
HWID                     : 0x00000002, 0x7d4eb2c0, 0x37ffa910, 0x75eb542b, 0x87707af6
Client Platform ID       : 0x000000ff
Company Name             : Microsoft Corporation
Issuer                   : SBTR
Scope                    : CON
Issued to machine        : CL-01
Issued to user           : SYSTEM
TS Locale ID             : 0x00000419
License ID               : A02-5.02-S
License Type             : Windows server 2003 TS temporary or permanent CAL.
Licensed Product Version : 0005.0002, Flag 0x80d48000
Valid from               : 8/7/2017 1:18:50 PM
Expires on               : 11/5/2017 1:18:50 PM

PS. Скрипт Get-TSCtst.ps1 .

Для симуляции различных ошибок  проверки сертификатов, существует прекрасный сайт — https://badssl.com/ , который предоставляет отличный набор тестов. В PowerShell 6.0 у командлетов Invoke-WebRequest/Invoke-RestMethod появился параметр –SkipCertificateCheck . Версия PowerShell v2.0  — Windows PowerShell 2.0 Deprecation. В PowerShell v3-5.1 — отсутствуют параметры для игнорирования ошибок связанных с сертификатами, поэтому воспользуемся сторонними методами.

Быстрый поиск в Google по теме игнорирование ошибок связанный с сертификатами дает большое количество различный способов.

Все примеры для PowerShell v5.1 и https://expired.badssl.com/

1 – 2 – не работают

3 – работает частично

4 – 5 – работают

 

Первоначальная ошибка:

er_06092017

 

 

1. [Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }            
Invoke-RestMethod https://expired.badssl.com/            
       
Invoke-RestMethod : The underlying connection was closed: An unexpected error occurred on a send.
At line:1 char:1
+ Invoke-RestMethod https://expired.badssl.com/
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
            
2. https://blogs.technet.microsoft.com/bshukla/2010/04/12/ignoring-ssl-trust-in-powershell-system-net-webclient/

$netAssembly = [Reflection.Assembly]::GetAssembly([System.Net.Configuration.SettingsSection])            
            
if($netAssembly)            
{            
    $bindingFlags = [Reflection.BindingFlags] "Static,GetProperty,NonPublic"            
    $settingsType = $netAssembly.GetType("System.Net.Configuration.SettingsSectionInternal")            
            
    $instance = $settingsType.InvokeMember("Section", $bindingFlags, $null, $null, @())            
            
 if($instance)            
    {            
        $bindingFlags = "NonPublic","Instance"            
        $useUnsafeHeaderParsingField = $settingsType.GetField("useUnsafeHeaderParsing", $bindingFlags)            
            
  if($useUnsafeHeaderParsingField)            
        {            
          $useUnsafeHeaderParsingField.SetValue($instance, $true)            
        }            
    }            
}            
            
Invoke-RestMethod : The underlying connection was closed: An unexpected error occurred on a send.
At line:1 char:1
+ Invoke-RestMethod https://expired.badssl.com/
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
            

3. [Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

Тут странности у меня – в 90% случаев отрабатывает, 10% – после перезапуска сессии, уже не отрабатывало. Проверяйте.

add-type @"
     using System.Net;
     using System.Security.Cryptography.X509Certificates;
     public class TrustAllCertsPolicy : ICertificatePolicy {
         public bool CheckValidationResult(
             ServicePoint srvPoint, X509Certificate certificate,
             WebRequest request, int certificateProblem) {
             return true;
         }
     }
"@            
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy            
Invoke-RestMethod https://expired.badssl.com/

 

С ошибкой:

witherr_06092017

 

Без ошибки:

withouterr_06092017

 

4. https://stackoverflow.com/questions/36456104/invoke-restmethod-ignore-self-signed-certs

if (-not("dummy" -as [type])) {            
    add-type -TypeDefinition @"
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

public static class Dummy {
    public static bool ReturnTrue(object sender,
        X509Certificate certificate,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors) { return true; }

    public static RemoteCertificateValidationCallback GetDelegate() {
        return new RemoteCertificateValidationCallback(Dummy.ReturnTrue);
    }
}
"@            
}            
            
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [dummy]::GetDelegate()            
            
Invoke-RestMethod https://expired.badssl.com/

 

При использовании Add-Type создается dll с случайным именем, а после удаляется. У тех, кто использует SRP/Aplocker — потребуются дополнительные действия для администратора. Утилита ProcMon – покажет, какие действия с файловой системой производит Add-Type. Сохраним вывод ProcMon в csv формате.


PS > Import-Csv AddType.CSV | Where Path -match "Temp" | Format-Table -Auto Operation,Path,Detail

Operation                     Path                                              Detail
---------                     ----                                              ------
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.tmp     Desired Access: Generic Write, Read Attri
CloseFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.tmp
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.0.cs    Desired Access: Generic Write, Read Attri
WriteFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.0.cs    Offset: 0, Length: 465, Priority: Normal
CloseFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.0.cs
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.dll     Desired Access: Generic Read/Write, Dispo
CloseFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.dll
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.cmdline Desired Access: Generic Write, Read Attri
WriteFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.cmdline Offset: 0, Length: 357, Priority: Normal
CloseFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.cmdline
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.out     Desired Access: Generic Write, Read Attri
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.err     Desired Access: Generic Write, Read Attri
WriteFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.out     Offset: 0, Length: 440, Priority: Normal
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.dll     Desired Access: Generic Read, Disposition
QueryStandardInformationFile  C:\Users\user\AppData\Local\Temp\ucp2kjeg.dll     AllocationSize: 4 096, EndOfFile: 3 584,
ReadFile                      C:\Users\user\AppData\Local\Temp\ucp2kjeg.dll     Offset: 0, Length: 3 584, Priority: Norma
CloseFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.dll
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.pdb     Desired Access: Read Attributes, Disposit
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.out     Desired Access: Read Attributes, Delete,
QueryAttributeTagFile         C:\Users\user\AppData\Local\Temp\ucp2kjeg.out     Attributes: A, ReparseTag: 0x0
SetDispositionInformationFile C:\Users\user\AppData\Local\Temp\ucp2kjeg.out     Delete: True
CloseFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.out
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.0.cs    Desired Access: Read Attributes, Delete,
QueryAttributeTagFile         C:\Users\user\AppData\Local\Temp\ucp2kjeg.0.cs    Attributes: A, ReparseTag: 0x0
SetDispositionInformationFile C:\Users\user\AppData\Local\Temp\ucp2kjeg.0.cs    Delete: True
CloseFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.0.cs
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.tmp     Desired Access: Read Attributes, Delete,
QueryAttributeTagFile         C:\Users\user\AppData\Local\Temp\ucp2kjeg.tmp     Attributes: A, ReparseTag: 0x0
SetDispositionInformationFile C:\Users\user\AppData\Local\Temp\ucp2kjeg.tmp     Delete: True
CloseFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.tmp
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.pdb     Desired Access: Read Attributes, Delete,
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.err     Desired Access: Read Attributes, Delete,
QueryAttributeTagFile         C:\Users\user\AppData\Local\Temp\ucp2kjeg.err     Attributes: A, ReparseTag: 0x0
SetDispositionInformationFile C:\Users\user\AppData\Local\Temp\ucp2kjeg.err     Delete: True
CloseFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.err
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.cmdline Desired Access: Read Attributes, Delete,
QueryAttributeTagFile         C:\Users\user\AppData\Local\Temp\ucp2kjeg.cmdline Attributes: A, ReparseTag: 0x0
SetDispositionInformationFile C:\Users\user\AppData\Local\Temp\ucp2kjeg.cmdline Delete: True
CloseFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.cmdline
CreateFile                    C:\Users\user\AppData\Local\Temp\ucp2kjeg.dll     Desired Access: Read Attributes, Delete,
QueryAttributeTagFile         C:\Users\user\AppData\Local\Temp\ucp2kjeg.dll     Attributes: A, ReparseTag: 0x0
SetDispositionInformationFile C:\Users\user\AppData\Local\Temp\ucp2kjeg.dll     Delete: True
CloseFile                     C:\Users\user\AppData\Local\Temp\ucp2kjeg.dll

 

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

Add-Type $code -OutputAssembly cert.dll

Для получение IL-кода — воспользуемся бесплатным продуктом dotPeek (существует большого количество декомпиляторов на любой вкус – ildasm, .Net Reflector, ILSpy и т.д.)

dotPeek – Load — Windows  — IL Viewer

 

dp_06092017

 

Получим код:

il_06092017



Создадим динамический модуль:

https://blogs.technet.microsoft.com/heyscriptingguy/2013/06/25/use-powershell-to-interact-with-the-windows-api-part-1/
https://blogs.technet.microsoft.com/heyscriptingguy/2013/06/26/use-powershell-to-interact-with-the-windows-api-part-2/
https://blogs.technet.microsoft.com/heyscriptingguy/2013/06/27/use-powershell-to-interact-with-the-windows-api-part-3/


# Module Builder            
$Domain = [AppDomain]::CurrentDomain            
$DynAssembly = New-Object System.Reflection.AssemblyName('Win32IC')            
            
# Запускать в памяти [System.Reflection.Emit.AssemblyBuilderAccess]::Run            
$AssemblyBuilder = $Domain.DefineDynamicAssembly(            
    $DynAssembly,             
    [System.Reflection.Emit.AssemblyBuilderAccess]::Run            
)             
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('Win32IC', $False)            
            
# [Dummy]::GetDelegate()            
$TypeBuilder = $ModuleBuilder.DefineType('Dummy', 'Public, Class')            
            
# Определим ReturnTrue метод            
$ReturnTrue = $TypeBuilder.DefineMethod(            
 # Название метода            
 'ReturnTrue',            
 # Атрибуты класса            
 [Reflection.MethodAttributes] 'Public, Static',            
 # Тип возврата            
 [bool],            
 # Тип параметров             
 [Type[]] @(            
  [object],            
  [System.Security.Cryptography.X509Certificates.X509Certificate],            
  [System.Security.Cryptography.X509Certificates.X509Chain],            
  [System.Net.Security.SslPolicyErrors]            
 )            
)             
            
$ilReturnTrue = $ReturnTrue.GetILGenerator()            
$ilReturnTrue.Emit([Reflection.Emit.OpCodes]::Ldc_I4_1)            
$ilReturnTrue.Emit([Reflection.Emit.OpCodes]::Ret)            
            
# Определим GetDelegate метод            
$GetDelegate = $TypeBuilder.DefineMethod(            
 'GetDelegate',            
 [Reflection.MethodAttributes] 'Public, Static',            
 [System.Net.Security.RemoteCertificateValidationCallback],            
 $null            
)            
            
$ctor = [System.Net.Security.RemoteCertificateValidationCallback].GetConstructor(            
 [type[]]@([object],[intptr])            
)            
            
$ilGetDelegate = $GetDelegate.GetILGenerator()            
$ilGetDelegate.Emit([Reflection.Emit.OpCodes]::Ldnull)            
$ilGetDelegate.Emit([Reflection.Emit.OpCodes]::Ldftn,$ReturnTrue)            
$ilGetDelegate.Emit([Reflection.Emit.OpCodes]::Newobj,$ctor)            
$ilGetDelegate.Emit([Reflection.Emit.OpCodes]::Ret)            
            
# Создание типа и вызов метода            
$Dummy = $TypeBuilder.CreateType()            
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = $Dummy::GetDelegate()            
            
#  Выполнияем запрос к сайту         
Invoke-RestMethod https://expired.badssl.com/

 

g4_06092017

 

5. Использование модуля Tunable-SSL-Validator

Описание проблемы — http://huddledmasses.org/blog/validating-self-signed-certificates-properly-from-powershell/

Скачать модуль — https://github.com/Jaykul/Tunable-SSL-Validator/archive/master.zip

В данной модуле Invoke-WebRequest/Invoke-RestMethod – переопределены в виде proxy function и добавляют дополнительную диагностическую информацию.


Get-Command -Module TunableSSLValidator -Verb Invoke            
            

CommandType     Name               Version    Source
-----------     ----               -------    ------
Function        Invoke-RestMethod  0.0        TunableSSLValidator
Function        Invoke-WebRequest  0.0        TunableSSLValidator

Дополнительная диагностическая информация:
wir_06092017

 

Добавим сертификат в исключение:

Add-SessionTrustedCertificate -LastFailed

e5_06092017


# Удалим сертификат            
Get-SessionTrustedCertificate | Remove-SessionTrustedCertificate            
            
# Отключим проверку            
Disable-SSLChainValidation            
            
Invoke-RestMethod https://expired.badssl.com/

 

l5_06092017

 

PS. Пример динамического кода из 4 пункта –RemoteCertificateValidationCallback .

Цель нашей лабораторной получить сертификат от доверенного центра сертификации для Skype For Business 2015:
    1. Выбор доверенного центра сертификации
        . Let’s Encrypt
    2. Выбрать способ проверки для получения сертификата
        . Windows DNS Server / Yandex PDD
    3. Определить список доменов в сертификате
        . sfb.contoso.ru,lyncdiscoverinternal.contoso.ru,web.contoso.ru,contoso.ru
    4. Получить сертификат
        . ACMESharp
        . DNSServer module
        . Yandex API
    5. Установить сертификат на сервер Skype For Business 2015
        . Import-CsCertificate
        . Set-CsCertificate
        . Get-CsCertificate

* В сертификате от Let’s Encrypt отсутствует CRL , поэтому может возникнуть проблема с запуском сервиса Fabric и требуется исправление ключа — <Parameter Name=”CrlCheckingFlag” Value=”0”

  https://itbasedtelco.wordpress.com/2016/06/14/s4b-front-end-servers-event-4097-flooding/

 

1. Выбор доверенного центра сертификации

Центры сертификации WoSign и StartCom, которые ранее выдавали сертификаты с большим сроком действия от 1 до 3 лет, перестали быть доверенными компанией Microsoft (https://blogs.technet.microsoft.com/mmpc/2017/08/08/microsoft-to-remove-wosign-and-startcom-certificates-in-windows-10/) и многими другими, хотя возможность получения и покупка сертификатов возможна.
К счастью, есть доверенный центр сертификации
Let’s Encrypt. Его функционала будет достаточно, для создания федерации с другими партнерами и взаимодействия с сервисом Skype. Для взаимодействия с облачными сервисами, требуется дополнительное тестирование.

Let’s Encrypt:
    Стоимость                            — бесплатно
    Сертификат действителен  — 90 дней
    SAN                                  — 100 доменов
    CRL                                   — не поддерживается
    OCSP                                — поддерживается
    IDN(Internationalized Domain Names) — поддерживается
    OV,EV                               — не поддерживается
    Способ проверки             — ftp,http,dns
    RSA размер ключа            — по умолчанию 2048 (2048, 3072, 4096)
    WildCard                            —
https://letsencrypt.org/2017/07/06/wildcard-certificates-coming-jan-2018.html
    Список ограничений       — https://letsencrypt.org/docs/rate-limits/

Список совместимости:
   
https://community.letsencrypt.org/t/which-browsers-and-operating-systems-support-lets-encrypt/4394
    https://letsencrypt.org/docs/certificate-compatibility/

Сроки реализации новых функций:
   
https://letsencrypt.org/upcoming-features/

# IDN (Internationalized Domain Names)             
[System.Globalization.IdnMapping]::new().GetAscii("президент.рф")            
xn--d1abbgf6aiiy.xn--p1ai                
            
[System.Globalization.IdnMapping]::new().GetUnicode("xn--d1abbgf6aiiy.xn--p1ai")            
президент.рф            

2. Выбрать способ проверки для получения сертификата

В данной лабораторной работе, для простоты был выбран способ проверки через записи DNS.

Рассмотрим вариант создания записей в бесплатном сервисе Яндекс.Почта(предоставляет функционал DNS сервера) и Windows DNS Server(должен быть опубликован в интернет).

Кому лень использовать скрипты, может с легкостью получить сертификаты через онлайн сервис — https://www.sslforfree.com/

Windows DnsServer Module — https://technet.microsoft.com/en-us/itpro/powershell/windows/dnsserver/dnsserver

    Get-DnsServerResourceRecord — https://technet.microsoft.com/en-us/itpro/powershell/windows/dnsserver/get-dnsserverresourcerecord

    Add-DnsServerResourceRecord — https://technet.microsoft.com/en-us/itpro/powershell/windows/dnsserver/add-dnsserverresourcerecord

    Set-DnsServerResourceRecord — https://technet.microsoft.com/en-us/itpro/powershell/windows/dnsserver/set-dnsserverresourcerecord

Yandex API Управление DNS — https://tech.yandex.ru/pdd/doc/concepts/api-dns-docpage/

    GET  /api2/admin/dns/list? — Получить DNS-записи домена —
https://tech.yandex.ru/pdd/doc/reference/dns-list-docpage/

    POST /api2/admin/dns/add   — Добавить DNS-запись        — https://tech.yandex.ru/pdd/doc/reference/dns-add-docpage/

    POST /api2/admin/dns/edit  — Редактировать DNS-запись   — https://tech.yandex.ru/pdd/doc/reference/dns-edit-docpage/

Как подключить себе сервис Яндекса:

   
https://blog.it-kb.ru/2016/02/03/delegating-dns-domain-to-yandex-ns-servers-and-connect-to-free-services-yandex-mail-for-domain-with-1000-unlimited-mailboxes-and-yandex-disk/

Все DNS записи будут типа TXT, формата:

name                       class  rr    text

_acme-challenge.domain   IN     TXT   "123drNmQL5vX0bu4YZlgy5wKNBlCny4yrjF1lSaUndc"

 

3. Определить список доменов в сертификате

Требования к сертифакату для Skype For Business 2015 —
https://technet.microsoft.com/en-us/library/dn933910.aspx

Воспользуемся мастером и скопируем список из вкладки Subject Alternative Name — https://blogs.technet.microsoft.com/uclobby/2015/05/15/renewing-skype-for-business-server-2015-certificates/

4. Получить сертификат

К этому пункту у нас должна быть собрана информация:

    + $DomainName    = "contoso.ru"

    + $SAN           = "sfb.contoso.ru","lyncdiscoverinternal.contoso.ru","web.contoso.ru","contoso.ru"

    + $email         = "pki@contoso.ru"

    * Yandex API — учетные данные, зарегистрированный и настроенный домен

+ — Обязательные параметры

* — Необязательные параметры

! — Все действия выполняем под учетной записью с правами Администратора

Для работы с Let’s Encrypt с помощью PowerShell , есть замечательный модуль ACMESharp.

Документация по модулю — https://pkisharp.github.io/ACMESharp-docs/

User Guide: ACMESharp PowerShell Client  — https://pkisharp.github.io/ACMESharp-docs/User-Guide.html

Quick Start: ACMESharp PowerShell Client — https://pkisharp.github.io/ACMESharp-docs/Quick-Start.html

ACMESharp создает Vault , которое содержит очень чувствительные данные.

* https://github.com/ebekker/ACMESharp/wiki/Vaults,-Vault-Providers-and-Vault-Profiles

:sys — the default system-wide Vault if you are running as an elevated user (admin)

    %ALLUSERSPROFILE%\ACMESharp\sysVault

:user — the default user-specific Vault if you are running as a non-elevated user

    %LOCALAPPDATA%\ACMESharp\userVault

Папки:

VAULT  /**/ = "00-VAULT"; // Vault Info

MTADT  /**/ = "01-MTADT"; // Asset Meta Data

PRVDR  /**/ = "10-PRVDR"; // Challenge Handler Provider Profile

INSTP  /**/ = "18-INSTP"; // Installer Provider Profile

CSRDT  /**/ = "30-CSRDT"; // CSR Generation Details

KEYGN  /**/ = "40-KEYGN"; // Private Key Generation Details

KEYPM  /**/ = "45-KEYPM"; // Private Key PEM Export

CSRGN  /**/ = "50-CSRGN"; // CSR Export

CSRPM  /**/ = "55-CSRPM"; // CSR PEM Export

CSRDR  /**/ = "56-CSRDR"; // CSR DER Export

CRTPM  /**/ = "65-CRTPM"; // Certificate PEM Export

CRTDR  /**/ = "66-CRTDR"; // Certificate DER Export

ISUPM  /**/ = "75-ISUPM"; // Issuer Certificate PEM Export

ISUDR  /**/ = "76-ISUDR"; // Issuer Certificate DER Export

ASSET  /**/ = "99-ASSET"; // Generic Asset

vault

Т.к. по умолчанию используется  EFS для защиты Vault ,а у тех где данный функционал отключен , надо будет явно отключить в файле конфигурации EFS — https://pkisharp.github.io/ACMESharp-docs/Local-Vault-EFS.html .

public string RootPath

        { get; set; }

public bool CreatePath

        { get; set; }

public bool BypassEFS

        { get; set; }

 

$param = (Get-ACMEVaultProfile).VaultParameters            
$param.Add("BypassEFS",$true)            
Set-ACMEVaultProfile -ProfileName ":user" -Provider local -VaultParameters $param  -Force

 

{

  "$type": "ACMESharp.Vault.Profile.VaultProfile, ACMESharp.Vault",

  "Name": "user",

  "ProviderName": "local",

  "ProviderParameters": null,

  "VaultParameters": {

    "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib",

    "BypassEFS": true,

    "CreatePath": true,

    "RootPath": "C:\\Users\\Administrator\\AppData\\Local\\ACMESharp\\userVault"

  }

}

 

Шаг 1: Установка модуля ACMESharp

Install-Module -Name ACMESharp -AllowClobber            
            

Данный модуль располагается в двух репозитариях PSGallery и Nuget. Версия в PSGallery новее и на момент написания 0.9.0.321:

Install-Module -Name ACMESharp -AllowClobber -Repository PSGallery -Force

Шаг 2: Инициализация хранилища

Initialize-ACMEVault
iv_25082017_1

Шаг 3: Регистрация аккаунта

* Let’s Encrypt CA поддерживает только тип email contact, URI формата mailto:<email-address>

New-ACMERegistration -Contacts mailto:$emailAcceptTos

Шаг 4: Подтверждение владением записей домена

В ACMESharp реализовано два типа ChallengeType: dns-01 , http-01 .

https-01 — описан https://pkisharp.github.io/ACMESharp-docs/Quick-Start.html

 

PS > Get-ACMEChallengeHandlerProfile -ListChallengeTypes

dns-01

http-01

PS > Get-ACMEChallengeHandlerProfile -ListChallengeHandlers

manual

Длинная запись:

 

New-ACMEIdentifier -Dns lyncdiscoverinternal.contoso.ru -Alias sfb1            
New-ACMEIdentifier -Dns sfb.contoso.ru -Alias sfb2            
New-ACMEIdentifier -Dns web.contoso.ru -Alias sfb3            
New-ACMEIdentifier -Dns contoso.ru -Alias sfb4            
            
Complete-ACMEChallenge -IdentifierRef sfb1 -ChallengeType dns-01 -Handler manual            
Complete-ACMEChallenge -IdentifierRef sfb2 -ChallengeType dns-01 -Handler manual            
Complete-ACMEChallenge -IdentifierRef sfb3 -ChallengeType dns-01 -Handler manual            
Complete-ACMEChallenge -IdentifierRef sfb4 -ChallengeType dns-01 -Handler manual             
            
Submit-ACMEChallenge -IdentifierRef sfb1 -ChallengeType dns-01            
Submit-ACMEChallenge -IdentifierRef sfb2 -ChallengeType dns-01            
Submit-ACMEChallenge -IdentifierRef sfb3 -ChallengeType dns-01            
Submit-ACMEChallenge -IdentifierRef sfb4 -ChallengeType dns-01            
            
## Give it a minute, just in case -- or go get more coffee            
 sleep -s 60            
            
Update-ACMEIdentifier -IdentifierRef sfb1            
Update-ACMEIdentifier -IdentifierRef sfb2            
Update-ACMEIdentifier -IdentifierRef sfb3            
Update-ACMEIdentifier -IdentifierRef sfb4

 

Короткая запись:

$SAN | Foreach-Object {$i=1} {            
 $id = "sfb$i"            
 New-ACMEIdentifier -Dns $_ -Alias $id | Out-Null            
 Complete-ACMEChallenge -IdentifierRef $id -ChallengeType dns-01 -Handler manual            
 $i++            
}
# Содержит ресурсные записи, которые потребуется создать на DNS сервере            
# Требуются для подтверждения владением доменом            
$DNS = (Get-ACMEVault).Identifiers.Where{$_.Alias -like "sfb*"}.Authorization.Challenges.Where{$_.Type -eq "dns-01"}.Challenge            
chdns_25082017_1            
# Создаем ресурсные записи             
# Windows DNS Server            
$AllRecords = (Get-DnsServerResourceRecord -ZoneName $DomainName -RRType Txt).Where{$_.Name -match "_acme-challenge"}            
            
$DNS.Foreach{            
 $rdns = $_            
 $name = $_.RecordName.TrimEnd($DomainName)            
 $record = $AllRecords.Where{$_.HostName -eq $name}            
 if($record)            
 {            
  $newrecord = $record.Clone()            
  $newrecord.RecordData.DescriptiveText = $_.RecordValue            
  Set-DnsServerResourceRecord -ZoneName $DomainName -NewInputObject $newrecord -OldInputObject $record            
 }            
 else            
 {            
  ADD-DnsServerResourceRecord -ZoneName $DomainName -Txt -Name $name -DescriptiveText $_.RecordValue            
 }            
}
# Yandex API            
Function Input-Captcha            
{            
 Param($src)            
             
 Add-Type -AssemblyName System.Windows.Forms,System.Drawing            
            
 $bimg  = [Net.WebClient]::new().DownloadData($src)            
 $ms    = [IO.MemoryStream]::new($bimg)            
 $img   = [Drawing.Image]::FromStream($ms)            
            
 [Windows.Forms.Application]::EnableVisualStyles();            
             
 $form = New-Object Windows.Forms.Form -Property @{            
  Text            = "Captcha"            
  Width           = 300            
  Height          = 227            
  FormBorderStyle = "FixedDialog"            
  Icon            = [Drawing.Icon]::ExtractAssociatedIcon(            
   "$env:ProgramFiles\Internet Explorer\iexplore.exe"            
  )            
 }            
             
 $pictureBox = New-Object Windows.Forms.PictureBox -Property @{            
  Width    = $img.Size.Width            
  Height   = $img.Size.Height            
  Location = New-Object Drawing.Size(60,30)            
  Image    = $img            
 }            
             
 $button = New-Object Windows.Forms.Button -Property @{            
  Location = New-Object Drawing.Size(12,153)            
  Size     = New-Object Drawing.Size(260,23)            
  Text     = "Set"            
 }            
             
 $button.Add_Click({            
  $script:rep = $textbox.Text.Trim()            
  $form.Close()            
 })            
            
 $textbox = New-Object Windows.Forms.TextBox -Property @{            
  Location = New-Object Drawing.Size(12,117)             
  Size     = New-Object Drawing.Size(260,20)             
 }            
             
 $form.Controls.Add($pictureBox)            
 $form.Controls.Add($button)            
 $form.Controls.Add($textbox)             
 $form.Add_Shown( { $form.Activate() } )            
 $form.ShowDialog() | Out-Null            
}            
            
Function Get-Token            
{            
 [CmdLetBinding()]            
 Param(            
  [String]$Url    = "https://pddimp.yandex.ru",            
  [String]$TUrl   = "https://pddimp.yandex.ru/api2/admin/get_token",            
  [Parameter(Mandatory)]            
   [PSCredential]$Credential,            
  [Parameter(Mandatory)]            
   [String]$DomainName            
 )            
             
 try {            
  # Логинемся в Yandex Passport            
  $wr = Invoke-WebRequest $TUrl -SessionVariable ya            
              
  # В форме заполняем login&passwd            
  $Form = $wr.Forms[0]            
  $Form.Fields["login"]  = $Credential.UserName            
  $Form.Fields["passwd"] = $Credential.GetNetworkCredential().Password            
              
  if($Form)            
  {            
   # Отправляем Post запрос            
   $wr = Invoke-WebRequest -Uri $Form.Action -WebSession $ya -Method POST -Body $Form            
               
   # Если успешно, то переходим к странице получения токена            
   $wr = Invoke-WebRequest $TUrl -WebSession $ya            
   if($wr.ParsedHtml.Title -eq "Admin's token management")            
   {            
    $Form = $wr.Forms[0]            
                
    # Удаляем из post запроса token_del             
    $Form.Fields.Remove("token_del") | Out-Null            
                
    # Вводим капчу            
    Input-Captcha $wr.Images.src            
                
    # Заполняем форму rep - Captcha            
    $Form.Fields["rep"]         = $rep             
    $Form.Fields["domain_name"] = $DomainName            
                
    # Отправляем Post запрос с методом token_get            
    $wr=Invoke-WebRequest -Uri "$url$($Form.Action)" -WebSession $ya -Method POST -Body $Form            
                
    # Возвращаем полученный токен            
    $wr.ParsedHtml.getElementsByTagName("Strong").Item(0).outerText            
   }            
  }            
 }            
 catch {            
  $_            
 }            
}            
            
$LUrl = 'https://pddimp.yandex.ru/api2/admin/dns/list?domain={0}' -f $DomainName            
$EUrl = 'https://pddimp.yandex.ru/api2/admin/dns/edit'            
$AUrl = 'https://pddimp.yandex.ru/api2/admin/dns/add'            
            
$PddToken = Get-Token -Credential "" -DomainName $DomainName            
            
if($PddToken)            
{            
 # Получим все записи для дальнейшего сравнения            
 $AllRecords = Invoke-WebRequest $LUrl -Headers @{            
  "accept" = "application/json"            
  PddToken = $PddToken            
 } | Foreach Content | ConvertFrom-Json | Foreach {$_.Records}            
             
 $DNS | Foreach {            
  $rdns = $_            
  $record = $AllRecords.Where{$_.FQDN -eq $rdns.RecordName}             
  if($record) {            
   # Обновляем данные            
   Invoke-WebRequest $EUrl -Headers @{"accept"="application/json" ; PddToken = $PddToken} -Method POST -Body @{            
    'domain'=$DomainName            
    'record_id'=$record.record_id            
    'content'=$rdns.RecordValue            
   }            
  }            
  else {            
   # Создаем записи            
   Invoke-WebRequest $AUrl -Headers @{"accept"="application/json" ; PddToken = $PddToken} -Method POST -Body @{            
    'domain'=$DomainName            
    'type'='TXT'            
    'subdomain' = $rdns.RecordName.TrimEnd($DomainName)            
    'content'=$rdns.RecordValue            
   }            
  }            
 }            
} 
 
Т.к. для подтверждения требуется вводить captcha вручную.
captcha_25082017_2           
 
# Отправить запрос на проверку            
(Get-ACMEIdentifier).Where{$_.Alias -match "sfb"}.Foreach{Submit-ACMEChallenge -IdentifierRef $_.Alias -ChallengeType dns-01}            
sleep -s 60            
(Get-ACMEIdentifier).Where{$_.Alias -match "sfb"}.Foreach{Update-ACMEIdentifier -IdentifierRef $_.Alias}

uacme_25082017_1

 

Шаг 5. Генерация сертификата

$idref = (Get-ACMEIdentifier).Where{$_.Alias -match "sfb"} | Select -First 1 | Foreach {$_.Alias}            
$aref =  (Get-ACMEIdentifier).Where{$_.Alias -match "sfb"} | Select -Skip 1 | Foreach {$_.Alias}            
New-ACMECertificate -Generate -IdentifierRef $idref -AlternativeIdentifierRefs $aref -Alias SfB2015FECert

new_25082017_1

# Отправить запрос            
Submit-ACMECertificate -CertificateRef SfB2015FECert

submit_25082017_1

Update-ACMECertificate -CertificateRef SfB2015FECert

update_25082017_1

 

Шаг 6. Экспорт в PFX

Get-ACMECertificate SfB2015FECert -ExportPkcs12 "SfB2015FECert.pfx" -CertificatePassword '12345678'
 
5. Установить сертификат на сервер Skype For Business 2015
 
Пример можно посмотреть - https://www.tbs-certificates.co.uk/FAQ/en/install-skype-business-server.html
 
Import-CsCertificate -Path "c:\SfB2015FECert.pfx" -PrivateKeyExportable $True -Password '12345678'            
Set-CsCertificate -Type Default, WebServicesInternal, WebServicesExternal,OAuthTokenIssuer -Thumbprint "439637470DF2D0E9DF933EADEA1F0610B3BA841C"            
            
Get-CsCertificate | Ft Thumbprint,Use            

Thumbprint                               Use
----------                               ---
439637470DF2D0E9DF933EADEA1F0610B3BA841C Default
439637470DF2D0E9DF933EADEA1F0610B3BA841C WebServicesInternal
439637470DF2D0E9DF933EADEA1F0610B3BA841C WebServicesExternal
439637470DF2D0E9DF933EADEA1F0610B3BA841C OAuthTokenIssuer

PS. Скрипты к статье:

le_windows_dns.txt -  Пример для Windows DNS Server

le_yandex_dns.txt    — Пример использования Yandex API PDD

В Windows 10 Version 1607 / Windows Server 2016 Microsoft добавили новый WMI провайдер WUAProvider для работы с обновлениями, который значительно облегчает  автоматизацию установки обновлений в Nano Server и при удаленном использовании интерфейса Windows Update Agent (ранее приходилось использовать PsExec/Task Scheduler). В предыдущих версиях ОС можно воспользоваться отличным модулем PSWindowsUpdate, для установки:

Install-Module PSWindowsUpdate -Scope AllUsers -Force

Список кодов возврата:

https://gist.github.com/AACJ/48a019ba0708a92d4ba60cc202d55e47

UpdateID:

https://www.catalog.update.microsoft.com

wu_22082017

В версиях Windows до Windows Server 2016, изменить значение Update можно:

# 0 - "Not configured"            
# 1 - "Disabled"            
# 2 - "Notify before download"            
# 3 - "Notify before installation"            
# 4 - "Scheduled installation"            
                     
$WUSettings = (New-Object -ComObject Microsoft.Update.AutoUpdate).Settings.NotificationLevel            
$WUSettings.NotificationLevel=2            
$WUSettings.Save()

Данный метод(SET) не работает в Windows Server 2016 (только GET) и можно воспользоваться изменением настроек через реестр/GPO/sconfig и т.д:

# HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU            
            
# AUOptions	Reg_DWORD             
# 2 = извещать перед загрузкой;            
# 3 = автоматически загружать и оповещать об установке;            
# 4 = автоматически загружать и начинать установку по расписанию (только при задании параметров scheduledDay и SheduledInstallTime);            
# 5 = требуется Automatic Updates, но пользователь может выполнить настройку самостоятельно            
            
# NoAutoUpdate Reg_DWORD            
# 0 = включить Automatic Updates;             
# 1 = отключить Automatic Updates            
            
Set-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU -Name AUOptions -Value 3

WUAProvider на данный момент документирован не полностью. Основной класс для работы MSFT_WUOperationsSession в котором реализовано 9 методов:

$wu = Get-CimClass -Namespace root/Microsoft/Windows/WindowsUpdate -ClassName MSFT_WUOperationsSession            
$wu.CimClassMethods            
            

Name                     ReturnType Parameters
----                     ---------- ----------
SetCallerID                  UInt32 {CallerID}
ApplyApplicableUpdates       UInt32 {HResult}
ScanForUpdates               UInt32 {OnlineScan, SearchCriteria, HResult, Updates}
ScanForUpdatesWithProxy      UInt32 {BypassList, BypassLocal, ProxyAddress, SearchCriteria...}
DownloadUpdates              UInt32 {Updates, HResult}
DownloadUpdatesWithProxy     UInt32 {BypassList, BypassLocal, ProxyAddress, Updates...}
InstallUpdates               UInt32 {Updates, HResult}
UninstallUpdates             UInt32 {Updates, HResult}
CheckWUOperationState        UInt32 {operationState}
 
# Поиск доступных обновлений - ScanForUpdates            
$ci = New-CimInstance -Namespace root/Microsoft/Windows/WindowsUpdate -ClassName MSFT_WUOperationsSession              
$result = $ci | Invoke-CimMethod -MethodName ScanForUpdates -Arguments @{SearchCriteria="IsInstalled=0";OnlineScan=$true}            

PS > $result

HResult ReturnValue Updates
------- ----------- -------
      0           0 {MSFT_WUUpdate (UpdateID = "ac3ad46a-f129-4ee7-bd4d-360385e87a1a")
	  
PS > $result.Updates

Description    : The Microsoft .NET Framework 4.7 is a highly compatible, in-place update for all the previous versions
                  of .NET Framework 4.X. After you install this update, you may have to restart your computer.
KBArticleID    :
MsrcSeverity   :
RevisionNumber : 202
Title          : Microsoft .NET Framework 4.7 for Windows 10 Version 1607 and Windows Server 2016 for x64 (KB3186568)
UpdateID       : ac3ad46a-f129-4ee7-bd4d-360385e87a1a
PSComputerName :

Description    : A security issue has been identified in a Microsoft software product that could affect your system. Yo
                 u can help protect your system by installing this update from Microsoft. For a complete listing of the
                  issues that are included in this update, see the associated Microsoft Knowledge Base article. After y
                 ou install this update, you may have to restart your system.
KBArticleID    :
MsrcSeverity   : Moderate
RevisionNumber : 201
Title          : 2017-08 Cumulative Update for Windows Server 2016 for x64-based Systems (KB4034658)
UpdateID       : fbf6de89-bf23-48b9-aef0-aae76f268708
PSComputerName :

Description    : Install this update to revise the definition files that are used to detect viruses, spyware, and other
                  potentially unwanted software. Once you have installed this item, it cannot be removed.
KBArticleID    :
MsrcSeverity   :
RevisionNumber : 200
Title          : Definition Update for Windows Defender - KB2267602 (Definition 1.249.1352.0)
UpdateID       : c9219ede-5e09-4639-a278-9cb27136e751
PSComputerName :
 
# Скачать обновление под индексом 2 - DownloadUpdates            
Invoke-CimMethod -InputObject $ci -MethodName DownloadUpdates -Arguments @{            
 Updates = [CimInstance[]]$result.Updates.Item(2)            
}            
HResult ReturnValue PSComputerName
------- ----------- --------------
      0           0
               
# Установить обновление под индексом 2 - InstallUpdates            
Invoke-CimMethod -InputObject $ci -MethodName InstallUpdates -Arguments @{            
 Updates = [CimInstance[]]$result.Updates.Item(2)            
}            

HResult ReturnValue PSComputerName
------- ----------- --------------
      0           0
# Выборочно скачать обновление (исключим для примера Microsoft .NET Framework 4.7)            
Invoke-CimMethod -InputObject $ci -MethodName DownloadUpdates -Arguments @{            
 Updates = [CimInstance[]]($result.Updates.Where{$_.UpdateID -ne 'ac3ad46a-f129-4ee7-bd4d-360385e87a1a'})            
}            

HResult ReturnValue PSComputerName
------- ----------- --------------
      0           0
               
# Выборочно установить обновление (исключим для примера Microsoft .NET Framework 4.7)            
Invoke-CimMethod -InputObject $ci -MethodName InstallUpdates -Arguments @{            
 Updates = [CimInstance[]]($result.Updates.Where{$_.UpdateID -ne 'ac3ad46a-f129-4ee7-bd4d-360385e87a1a'})            
}            

HResult ReturnValue PSComputerName
------- ----------- --------------
      0           0
 
# Установить все обновления - ApplyApplicableUpdates            
Invoke-CimMethod -InputObject $ci -MethodName ApplyApplicableUpdates            

HResult ReturnValue PSComputerName
------- ----------- --------------
      0           0
 
# Текущий статус операции            
Invoke-CimMethod -InputObject $ci -MethodName CheckWUOperationState            

operationState ReturnValue PSComputerName
-------------- ----------- --------------
Idle                     0

operationState ReturnValue PSComputerName
-------------- ----------- --------------
Downloading              0

operationState ReturnValue PSComputerName
-------------- ----------- --------------
Installing               0
 
# Список установленных обновлений            
$ci = New-CimInstance -Namespace root/Microsoft/Windows/WindowsUpdate -ClassName MSFT_WUOperationsSession            
$result = $ci | Invoke-CimMethod -MethodName ScanForUpdates -Arguments @{SearchCriteria="IsInstalled=1";OnlineScan=$true}            
$result.Updates

Отдельно стоит уточнить про метод UninstallUpdates при вызове данного метода для обновлений, которые устанавливались не через WSUS , данный метод вернет — 2149842984|WU_E_UNINSTALL_NOT_ALLOWED|uninstall is not allowed due to non managed environment .  В модуле PSWindowsUpdate для удаления используется утилита wusa.exe .

# Удалить обновление под индексом 2 - UninstallUpdates            
Invoke-CimMethod -InputObject $ci -MethodName UninstallUpdates -Arguments @{            
 Updates = [CimInstance[]]$result.Updates.Item(2)            
}

Полезные ссылки:

https://docs.microsoft.com/en-us/windows-server/get-started/manage-nano-server

https://blogs.technet.microsoft.com/nanoserver/2016/01/16/updating-nano-server-using-windows-update-or-windows-server-update-service/

https://blogs.technet.microsoft.com/nanoserver/2016/10/07/updating-nano-server/