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

В рамках данной статьи, опишу процесс автоматической установки и настройки 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 .