Показаны сообщения с ярлыком Powershell. Показать все сообщения
Показаны сообщения с ярлыком Powershell. Показать все сообщения

вторник, 6 декабря 2011 г.

Особенности параметра–Filter

Сегодня в почту пришло письмо от многоуважаемого коллеги. К слову, как хорошо, когда задают вопросы. Это позволяет столкнуться с задачами и проблемами, с которыми раньше не сталкивался. Так вот:
Андрей, добрый день! У меня возник ещё один вопрос, т.к. я столкнулся с некорректной, на мой взгляд ,работой ключевого командлета Get-ADUser Задача состояла в том, чтоб выбрать членов указанной группы, для этого я написал простую, на мой взгляд, конструкцию:
Get-ADUser  –Properties * -Filter ‘memberof –like “*abc*”’ | ft name 
На выходе получаю пустой список Когда я немножко так сказать усложнил путь:
Get-ADUser –Properties * -Filter * | where {$_.memberof  –like “*abc*”} | ft name

То всё сработало чудесно.. Отсюда вопрос – почему так, и можно ли вообще доверять опции  -filter  данного командлета? Конечно, я могу пользоваться и вторым вариантом, однако это вызывает определённые неудобства связанные с работой в удаленных сессиях, т.к. объем выборки очень большой, и сессия обрывается..

И, мой ответ.
Здравствуйте!
Что тут стоит сказать. Прежде всего вот http://technet.microsoft.com/en-us/library/hh531527(WS.10).aspx. Обратите внимание на 11 пример. По сути получается примерно следующее. Атрибут memberof, это не совсем обычный атрибут. Это бэклинк (back-link) - вычисляемое значение. Мало того, согласно его описанию вот тут http://msdn.microsoft.com/en-us/library/ms677943.aspx, он может хранить не все группы, членом которых является пользователь, а только самую нижнюю в иерархии. Например, если пользователь является членом группы А и Б, а Б в свою очередь входит в С то группы С в списке не будет. По-этому с этим нужно работать совсем иначе. Для него требуется совсем другой синтаксис LDAP запроса, с указанием matching rule OID, который, при обработке запроса LDAP сервером вызовет рекурсивный обход всех вложенных объектов начиная от потомка в сторону предков.
Как-то так:
filter1
Указывая параметр filter, Вы задаете LDAP фильтр, который отрабатывает на сервере и возвращает уже отфильтрованный результат. А вот второй запрос
Get-ADUser –Properties * -Filter * | where {$_.memberof  –like “*abc*”} | ft name
работает немного иначе. Обратите внимание на вывод get-member. В нем атрибут memberof уже является обычной строкой. Поэтому этот oneliner работает без проблем, но количество объектов, которое он возвращает может быть очень большим.
Посему мне кажется, что значительно проще в этом плане использовать комадлет get-adgroupmember:filter2

Итого. Доверять опции -filter можно и нужно. Просто данный конкретный случай - особенность.
В общем, пишите, задавайте вопросы! Разберемся!

вторник, 22 ноября 2011 г.

Сколько проживет SSD

Немного провокационный заголовок, не так ли? На самом деле я не собираюсь тут приводить процедуру расчета времени жизни SSD. Хочу написать о другом. Сегодня коллега задал вопрос: “Есть ли счетчик, который помог бы определить, сколько данных записано на диск. И вообще, как определить количество данных, записанных на диск?”. Собственно тут хотел бы привести свое видение ответа, так сказать.
Как оказалось – нет в perfmon такого счетчика. Ну, нет и все тут. Вероятно, есть на то какие-то причины. Но интересующие данные, судя по всему, получить таки можно. На ответ наткнулся совсем случайно. Для того чтобы получить нужные цифры можно воспользоваться утилитой fsutil. А конкретно, командой, например fsutil fsinfo statistics g: Утилита возвращает кучу всего, и, в частности значение UserFileWriteBytes, которое, похоже, и показывает нужную информацию. Стоит отметить, что эти данные, видимо, сбрасываются при перезагрузке. Ну и в качестве тренировки написал скрипт, который парсит все это счастье и возвращает хэш-таблицу с нужными данными:
function Get-DiskStats ($diskLetter){
 if (Test-Path $diskLetter) {
    (fsutil fsinfo statistics $diskLetter) | `
    where {$_.length -ne 0} | `
    %{$_.replace(" ","")} | `
    % {$_ -match "(?\w+):(?[0-9]{1,})"; $matches} | `
    % {$ret = @{}} {$ret["$($_.name)"]=$_.value}
    return $ret}
 else {Write-Error "There is no disk $diskLetter"; return $null}
}

Get-DiskStats g:

Подозреваю правда, что информацию можно получить так же при помощи ETW. Вот только как – не углублялся пока. PS. Некоторые изменения к плану. Если вы хотите собрать данные с работающего сервера за некоторый промежуток времени - вы можете поступить следующим образом.
1. Получить статистику на начальный момент времени:
Get-DiskStats c: | Export-Clixml c:\Files\disk.xml
Этим самым вы сохраните начальные данные в файл xml.
2. На конечный момент можно поступить следующим образом:
$old = Import-Clixml c:\Files\disk.xml
$new = Get-DiskStats c:


$old.Keys | ? {$old[$_] -ne $new[$_]} | % {$obj = New-Object -TypeName psobject;
             $obj | Add-Member -MemberType NoteProperty -Name name -Value $_;
             $obj | Add-Member -MemberType NoteProperty -Name oldValue -Value $old[$_];
             $obj | Add-Member -MemberType NoteProperty -Name newValue -Value $new[$_];
             $obj} `
             | sort name | ft -AutoSize
Что вернет вам таблицу со сравнениями старых и новых значений. Вот, примерно такую:
name                oldValue   newValue   
----                --------   --------   
BitmapReadBytes     12632064   12673024   
BitmapReads         534        544        
BitmapWriteBytes    134942720  143458304  
BitmapWrites        28034      29936      
LogFileWriteBytes   1601335296 1709424640 
LogFileWrites       214884     230697     

вторник, 4 октября 2011 г.

Получения списка выданных dhcp адресов скриптом

Всем привет. Недавно в форуме technet появился вопрос: как получить список выданных адресов и когда они были выданы. После короткого погружения в тему выяснилось, что используя стандартный API узнать это нельзя, поскольку сервер, похоже, таких данных не хранит, ну или не предоставляет. Коллега, в обсуждении, предложил парсить журнал сервера с тем чтобы достать из журнала нужные данные. Однако этот вариант требовал написания достаточно сложной для меня схемы, с отслеживание состояний и изменений, происходящих с адресом. С другой стороны, можно было использовать netsh для получения данных о выданных адресах, например вот так: netsh dhcp server scope 172.20.0.0 show clients 1. Однако этот способ не возвращает данных о дате выдачи, только о времени окончания. В итоге мы пришли к мнению, что проще использовать скрипт, и рассчитать нужную дату. Но при подробном изучении и этот способ оказался несколько более сложным, чем мне казалось на первый взгляд. Проблема в том, что я очень не люблю, наверное потому что не умею, парсить строки. Это вызывает у меня баттхёрт и головокружение. В итоге я воспользовался вот этим скриптом и слегка его изменил исключительно в целях пруф оф концепт:

$DHCP_EnumSubnetClients = @'          
[DllImport("dhcpsapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern uint DhcpEnumSubnetClients(
string ServerIpAddress,
uint SubnetAddress,
ref uint ResumeHandle,
uint PreferredMaximum,
out IntPtr ClientInfo,
ref uint ElementsRead,
ref uint ElementsTotal);
'@

$DHCP_Structs = @'
namespace mystruct {
using System;
using System.Runtime.InteropServices;

public struct CUSTOM_CLIENT_INFO
{
public string ClientName;
public string IpAddress;
public string MacAddress;
}

[StructLayout(LayoutKind.Sequential)]
public struct DHCP_CLIENT_INFO_ARRAY
{
public uint NumElements;
public IntPtr Clients;
}

[StructLayout(LayoutKind.Sequential)]
public struct DHCP_CLIENT_UID
{
public uint DataLength;
public IntPtr Data;
}

[StructLayout(LayoutKind.Sequential)]
public struct DATE_TIME
{
public uint dwLowDateTime;
public uint dwHighDateTime;

public DateTime Convert()
{
if (dwHighDateTime== 0 && dwLowDateTime == 0)
{
return DateTime.MinValue;
}
if (dwHighDateTime == int.MaxValue && dwLowDateTime == UInt32.MaxValue)
{
return DateTime.MaxValue;
}
return DateTime.FromFileTime((((long) dwHighDateTime) << 32) | (UInt32) dwLowDateTime);
}
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct DHCP_HOST_INFO
{
public uint IpAddress;
public string NetBiosName;
public string HostName;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct DHCP_CLIENT_INFO
{
public uint ClientIpAddress;
public uint SubnetMask;
public DHCP_CLIENT_UID ClientHardwareAddress;
[MarshalAs(UnmanagedType.LPWStr)]
public string ClientName;
[MarshalAs(UnmanagedType.LPWStr)]
public string ClientComment;
public DATE_TIME ClientLeaseExpires;
public DHCP_HOST_INFO OwnerHost;
}
}
'@

$resumeHandle = 0
$clientInfo = 0
$ElementsRead = 0
$ElementsTotal = 0

Add-Type $DHCP_Structs
Add-Type -MemberDefinition $DHCP_EnumSubnetClients -Name GetDHCPInfo -Namespace Win32DHCP

$DHCPServerIP = "172.20.10.10" # change this to match your DHCP server IP

[void][Win32DHCP.GetDHCPInfo]::DhcpEnumSubnetClients($DHCPServerIP,0,[ref]$resumeHandle,0,[ref]$clientInfo,[ref]$ElementsRead,[ref]$ElementsTotal)

$clients = [system.runtime.interopservices.marshal]::PtrToStructure($clientInfo,[mystruct.DHCP_CLIENT_INFO_ARRAY])

[int]$size = $clients.NumElements
[int]$current = $clients.Clients
$ptr_array = new-object system.intptr[]($size)
$current = new-object system.intptr($current)

for ($i=0;$i -lt $size;$i++)
{
$ptr_array[$i] = [system.runtime.interopservices.marshal]::ReadIntPtr($current)
$current = $current + [system.runtime.interopservices.marshal]::SizeOf([system.IntPtr])
}

function uIntToIP {
param ($intIP)
$objIP = new-object system.net.ipaddress($intIP)
$arrIP = $objIP.IPAddressToString.split(".")
return $arrIP[3] + "." + $arrIP[2] + "." + $arrIP[1] + "." + $arrIP[0]
}

[array]$clients_array = new-object mystruct.CUSTOM_CLIENT_INFO

for ($i=0;$i -lt $size;$i++)
{
$objDHCPInfo = New-Object psobject
$current_element = [system.runtime.interopservices.marshal]::PtrToStructure($ptr_array[$i],[mystruct.DHCP_CLIENT_INFO])
add-member -inputobject $objDHCPInfo -memberType noteproperty -name ClientIP -value $(uIntToIP $current_element.ClientIpAddress)
add-member -inputobject $objDHCPInfo -memberType noteproperty -name ClientName -value $current_element.ClientName
add-member -inputobject $objDHCPInfo -memberType noteproperty -name OwnerIP -value $(uIntToIP $current_element.Ownerhost.IpAddress)
add-member -inputobject $objDHCPInfo -memberType noteproperty -name OwnerName -value $current_element.Ownerhost.NetBiosName
add-member -inputobject $objDHCPInfo -memberType noteproperty -name SubnetMask -value $(uIntToIP $current_element.SubnetMask)
add-member -inputobject $objDHCPInfo -memberType noteproperty -name LeaseExpires -value $current_element.ClientLeaseExpires.Convert()
if (($current_element.ClientLeaseExpires.Convert()).Year -ne 9999)
{add-member -inputobject $objDHCPInfo -memberType noteproperty -name LeaseObtained -value ($current_element.ClientLeaseExpires.Convert()).AddDays(-8)}

$mac = [System.String]::Format(
"{0:x2}-{1:x2}-{2:x2}-{3:x2}-{4:x2}-{5:x2}",
[system.runtime.interopservices.marshal]::ReadByte($current_element.ClientHardwareAddress.Data),
[system.runtime.interopservices.marshal]::ReadByte($current_element.ClientHardwareAddress.Data, 1),
[system.runtime.interopservices.marshal]::ReadByte($current_element.ClientHardwareAddress.Data, 2),
[system.runtime.interopservices.marshal]::ReadByte($current_element.ClientHardwareAddress.Data, 3),
[system.runtime.interopservices.marshal]::ReadByte($current_element.ClientHardwareAddress.Data, 4),
[system.runtime.interopservices.marshal]::ReadByte($current_element.ClientHardwareAddress.Data, 5)
)

add-member -inputobject $objDHCPInfo -memberType noteproperty -name MacAddress -value $mac
$objDHCPInfo
}

По сути я вставил только одну строку:

if (($current_element.ClientLeaseExpires.Convert()).Year -ne 9999) 
{add-member -inputobject $objDHCPInfo -memberType noteproperty -name LeaseObtained -value ($current_element.ClientLeaseExpires.Convert()).AddDays(-8)}
Которая добавляет дополнительное поле в выводимый объект. Значение этого поля равно значению параметра LeaseExpires - 8 дней. Можно поправить как себе задумается. В принципе можно докрутить все это счастье и узнавать на сколько был выдан адрес из параметров скоупа. Но это уже совсем другая история.

понедельник, 3 октября 2011 г.

Powershell 3

Свершилось! Группа разработки Powershell в своем блоге опубликовала, правда довольно давно, заметку о новом powershell 3 и о его возможностях. В принципе, все движется к тому, что управление новыми клинтскими и серверными ОС будет основано на нем. Особенно учитывая тот факт, что в сессии, посвященной введению в Windows Server 8, было сказано что основным типом инсталляции нового сервера будет Server Core. В общем, учим.

среда, 10 августа 2011 г.

Копипаст в виртуалку

В процессе работы понадобилось передать содержимое всяких текстовых файликов в виртуалки на основе hyperv. При этом очень хотелось не подключать их ни к общей сети ни к сети компьютера-хоста. В общем – без всяких там RDP и иже с ними. Просто скопировать и вставить. Для этих целей есть встроенная возможность:

typeText

Однако если попытаться вставить большой объем текста – получается следующее:typeTextErr


Собственно говоря, покопавшись в гуглах я обнаружил, что есть WMI Классы для работы с hyperv. Собственно на основе их родилась вот такая вот функция. Особенность функции в том, что тот метод, который зовется из WMI, принимает на вход только ascii строку – 7 бит. А значит, русский шрифт, как не прискорбно, передать не выйдет. Ну да и черт с ним, хотя, конечно, печалька.

К слову говоря, sleep тут нужен по причине того, что без него работает с некоторыми странностями.
<#
.SYNOPSIS
Отправка текста в буфере обмена в виртуальную машину hyper-v

.DESCRIPTION
Текст, находящийся в буфере обмена отправляется в виртуальную машину
методом TypeText wmi класса Msvm_Keyboard. Попросту говоря при передаче текста
внутри виртуальной машины эмулируется нажатие соответствующих клавиш.
Для использования функции необходимо наличие в буфере обмена текста, и открытое активное
окно редактора текста в виртуальной машине

.PARAMETER $VM
Имя виртуальной машины.

.PARAMETER $Server
Имя hyper-v сервера. Поумолчанию используется текущая машина

.EXAMPLE
PS C:\>send-clipboardtext -vm pkidemosubordinateca1

Пример вызова функции

.INPUTS
System.String,System.String
#>
function send-clipboardText {
[CmdletBinding()]
param(
[parameter(Position=0 , Mandatory = $true, ValueFromPipeline = $false)] $VM,
[String]$Server="."
)
begin {
try {
}
catch {
}
}
process {
try {
$text = ([System.Windows.Forms.Clipboard]::GetText()) -split "`n"
$ascii = [System.Text.Encoding]::ASCII

if ($VM -is [String]) {$VM=(Get-VM -Name $VM -Server $Server) }
if ($VM -is [System.Management.ManagementObject]) {
$VSMKB=($VM.getRelated("Msvm_Keyboard") | select-object)
if ($text -ne $null ) {
foreach ($line in $text){
$bytes = $ascii.getBytes($line)
$line = $ascii.getString($bytes)
$result = $VSMKB.TypeText($line)
if ($result.returnvalue -eq 0) {write-host "'$line' was successfully sent to $vm on $server"}
sleep -Milliseconds ($line.Length*10)
}
}
}
}
catch {
echo "oops!"
$Error
}
}
end {
try {
}
catch {
}
}
}

Ну и пример вызова
send-clipboardtext -vm contosoca1

среда, 13 июля 2011 г.

Несколько статей в журналах.

Всем привет. Вот, надумал положить тут пару ссылок на мои недавние статьи в печатных изданиях.

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

Мониторинг своими руками
Описание технологий CIM, CIMOM, WBEM, WMI, WINRM/WINRS, WSMAN, POWERSHELL REMOTING, их возможности и немного истории.

Мониторинг основных показателей приложений. Часть 1.
О том что нужно мониторить, и как это можно делать. Будет и продолжение, но, к сожалению сейчас совсем нет времени. С другой стороны, его обязательно нужно найти.

воскресенье, 23 января 2011 г.

Непонятный TrustedHosts

На днях мой коллега задал мне вопрос, на который я не смог ответить с ходу. Ему не удавалось подключиться, с помощью powershell remoting, к удаленному серверу, находящемуся в другом домене. При попытке сделать это он получал примерно вот такое сообщение

Не удалось подключиться к удаленному серверу. Сообщение об ошибке: Клиенту WinRM не удается обработать запрос. Проверку подлинности по умолчанию можно использовать с IP-адресом при следующ
их условиях: транспортом является HTTPS или назначением является список TrustedHosts, кроме того, должны быть предоставлены явно указанные учетные данные. Чтобы настроить TrustedHosts, используйте winrm.
cmd. Обратите внимание, что в списке TrustedHosts могут находиться компьютеры, не прошедшие проверку подлинности. Для получения сведений о настройке TrustedHosts используйте следующую команду: winrm help
config. Дополнительные сведения см. в разделе справки, вызываемом командой about_Remote_Troubleshooting.

Я, в свою очередь, тоже попробовал сделать это в разных окружениях и получил несколько разных результатов:

Если клиент не входит в домен

IP Address

После нескольких экспериментов мой коллега сообщил, что если добавить адрес удаленного сервера в TrustedHosts на клиентской машине, то все работает. Все довольно странно и запутано. По этому – давайте подробнее разберем все эти ситуации.

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

  • компьютеры сервера и клиента расположены в разных доменах
  • компьютер сервера расположен в домене а клиент – вне домена
  • компьютеры сервера и клиента не входят в домен

Кроме того, исходя из сообщений об ошибках (да, их надо читать внимательно), и, соответственно, из методов обращения к серверу, есть две ситуации:

  • при обращении к серверу, клиент указывает IP-адрес в качестве адреса назначения
  • при обращении к серверу, клиент указывает имя в качестве адреса назначения

К сожалению в интернетах нет однозначного мнения по поводу того, где, на сервере или клиенте, нужно задавать параметр TrustedHosts, и нужно ли это вообще делать. На что это влияет и все такое. В целом, информация довольно скудная по этому поводу. Поэтому, после долгого забега по гуглу, я решил обратиться, куда бы вы думали – да, к первоисточнику. А именно – к спецификации на ws-management.  Про параметр TrustedHosts там сказано следующее:

TrustedHosts: Contains host names to which the Web Services Management Protocol Extensions for Windows Vista clients are allowed to send requests by using an authentication scheme and transport that does not allow the client to authenticate the service, such as Basic over HTTP.

The specified host names may be either Internet host names or IP addresses. TrustedHosts

  • Blank: No hosts are trusted.
  • The asterisk "*" character: All hosts are trusted.
  • A list of host name patterns separated by the comma "," character, in which each host name can be one of four possible values:
    • String starting with the asterisk "*" character and containing at least two characters. All hosts that share the suffix are trusted.
    • String ending with the asterisk "*" character and containing at least two characters. All hosts that share the prefix are trusted.
    • The exact string "": All NetBIOS names are trusted (for example, strings that do not contain the period "." character).
    • A string without the asterisk "*" character: The host named by the string is trusted.

The default value for the <TrustedHosts> element MUST be a blank string.

Основная идея выделена. И это означает, что параметр TrustedHosts нужно использовать на клиенте. В этом списке указываются машины, при работе с которыми невозможно использовать схемы с поддержкой взаимной аутентификации. Таким образом, если и клиент и сервер находятся в одном домене, то для подключения вы можете использовать простую команду без дополнительных параметров.

Следующий этап – два домена с доверительными отношениями. В этом случае тоже нет необходимости в TrustedHosts. Однако необходимо обеспечить корректное разрешение имен. Однако, скажете вы, ведь в домене можно обращаться не только по имени, а еще и по адресу, и все должно работать корректно. А у нас, при попытке использовать адрес, вываливается ошибка. И вот тут возникает необходимость внимательно прочитать содержимое ошибки:

Проверку подлинности по умолчанию можно использовать с IP-адресом при следующих условиях: Транспортом является HTTPS или назначением является список TrustedHosts.

Прежде всего видим, что в TrustedHosts нужно указывать именно назначение. А почему такое ограничение на использование IP-адреса? Об этом написано в блоге Ask the Directory Services Team. Вкратце, там сказано, что нельзя в качестве использовать IP-адреса в синтаксисе записи SPN. И если в windows xp/2003 это работало то в Vista и старше при использовании IP-адреса в назначении не будет даже попыток использовать kerberos . Отсюда и ошибка: раз не используется протокол с взаимной аутентификацией – используйте HTTPS или пропишите TrustedHosts. Для того чтобы убедиться в этом, вы можете воспользоваться вашим любимым сниффером. В нем вы увидите, что клиент даже не пытается установить связь с сервером, если вы указываете в качестве назначения адрес. Ошибка выдается сразу. Более подробно про SPN вы можете так же прочесть в моей статье.

И наконец – не доменное окружение. Powershell знает, что клиентская машина не в домене. Значит kerberos по определению не возможен. Следовательно либо TrustedHosts либо HTTPS.