Книга про PowerShell

Не так давно знайома, яка працює в одному з книжкових видавництв, звернулася до мене з пропозицією написати книгу про PowerShell. «Тільки з докладним описом що та як, а не так як на Хабре», пролунав її приємний голос по той бік телефону причому без всякого докору, хоча останнє зауваження цілком справедливо і могло бути інтонаційно підкреслено. Якщо чесно, я не довіряю людям, тим більше жінкам, а тому попросив дати час на роздуми.

Гра не варто свечь?
А скільки коштують самі свічки? З урахуванням курсу іноземних валют — чимало. Простіше розщедритися на дизель-генератор, тим паче що від свечь користі, крім як від джерела освітлення, ніякого, а від генератора хоч електрика. Втім, метафора так собі, давайте снизойдем до звичного IT-шнику стилю.
Написання оригінальної книги вимагає досить багато часу. Під оригінальністю я розумію не вільний переказ специфікації або довідкового посібника з мови, зазвичай йде в комплекті, а те, що як раз за гранню останніх. На жаль, але переважна більшість книг є саме вільним переказом, на який, за заявою самих авторів у передмові, вони витрачають від декількох місяців до декількох років. Термін, потрібно визнати, чималий, адже за цей період можуть відбутися певні зміни в самій мові, в результаті чого роль подібних книг зменшується ще більше. Може бути, я і не правий, але на мій особистий погляд читання документації, поєднане з різного роду експериментами, дають набагато більше, ніж годинник, що витрачаються на вільний переказ тієї ж документації з повторенням прикладів з останнього, іншими словами хорошим стартом в освоєнні чого-небудь є саме документація та особистий досвід, а розширенням кругозору — блоги або просто невеликі замітки на різних інтернет-майданчиках, корисних серед них, втім, також мало.

Докладно — це як?
При написанні книги неодмінно повинні існувати межі, в іншому випадку можна отримати не наукову монографію, а щось з розряду цікавих відомостей, які на Заході зазвичай прийнято іменувати cookbook. Останній вигляд книг виключно шкідливий, так як, по-перше, формує шаблонність мислення, по-друге, нічого крім фрагментарності в знаннях читає не отримає. Якщо розглядати на конкретних прикладах, то давайте уявимо собі ситуацію: ви працюєте над головою, присвяченій мережі і все що з нею пов'язано, і хочете продемонструвати читачеві, скажімо, як отримати свій MAC-адресу, — в залежності від контексту, рішень у задачі дуже багато, але чи станете ви описувати їх всі, попутно пояснюючи ніж один спосіб краще іншого, а третій — попереднього? Відповідь, гадаю, очевидна. Письменник повинен вміти відрізняти другорядне від дійсно корисних і значущих фактів, при цьому даючи зрозуміти, що приводиться приклад коду являє собою лише один з окремих випадків, тим самим стимулюючи інтерес читача до власних експериментів. Єдине, мабуть, що не повинен поступатися письменник — упомянания про сумісність версій і помилки, допущені самими розробниками .NET платформи.

Приклади отримання MAC-адреси в PowerShell
function Get-MacAddress {
<#
.NOTES
Альтернативні способи отримання MAC-адреси.

Приклад 1:
$asm = Add-Type -MemberDefinition @'
[DllImport("rpcrt4.dll")]
public static extern Int32 UuidCreateSequential(
out Guid guid
);
'@ -Name Uuid -NameSpace MacAddress -PassThru
$guid = New Object Guid

if (($res = $asm::UuidCreateSequential([ref]$guid)) -ne 0) {
(New-Object ComponentModel.Win32Exception($res)).Message
break
}
[Regex]::Replace(
$guid.Guid.Split('-')[-1], '.{2}', '$0-'
).TrimEnd('-')

Приклад 2:
Get-WmiObject Win32_NetworkAdapter | Where-Object {
$_.MacAddress
} | ForEach-Object {
New-Object PSObject -Property @{
Description = $_.Description
Service = $_.ServiceName
MACAddress = $_.MACAddress
}
} | Select-Object Description, Service, MACAddress

Приклад 3:
Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {
$_.MacAddress
} | ForEach-Object {
New-Object PSObject -Property @{
Description = $_.Description
Id = $_.SettingID
MACAddress = $_.MACAddress
}
} | Select-Object Description, Id, MACAddress

Приклад 4:
$mac, $nic = (getmac /fo csv | Where-Object {
![String]::IsNullOrEmpty($_) -and $_ -match '\w{2}\-'
}).Split(',') | ForEach-Object {$_.Trim('"')}
New-Object PSObject -Property @{
Interface = $nic.Substring(($$ = $nic.IndexOf('{')), $nic.Length - $$)
MACAddress = $mac
}

Приклад 5:
$key = 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\*'
Get-ItemProperty $key | Where-Object {
$_.DhcpIpAddress -and $_.DhcpIpAddress -ne '0.0.0.0'
} | Select-Object DhcpIpAddress | ForEach-Object {
New-Object PSObject -Property @{
IPAddress = $_.DhcpIpAddress
MACAddress = ([Regex]'(\w{2}\-){5}\w{2}').Match((
nbtstat -a $_.DhcpIpAddress
)).Value
}
}

Приклад 6:
([Regex]'(\w{2}\-){5}\w{2}').Match((
ipconfig /all
)).Value

Приклад 7:
([Regex]'(\w{2}\s){6}').Match((
route print
)).Value.Trim().Replace([Char]32, '-')
#>

#в CLR v4 немає помилки доступу по дорозі Global\.net clr networking
#для обліковий запис з обмеженими правами
if (($clr = $PSVersionTable.CLRVersion.Major) -ge 4) {
[Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() |
Where-Object {
$_.OperationalStatus -eq [Net.NetworkInformation.OperationalStatus]::Up
} | ForEach-Object {
if (![String]::IsNullOrEmpty((
$$ = $_.GetPhysicalAddress().ToString()
))) {
New-Object PSObject -Property @{
Id = $_.Id
MACAddress = [Regex]::Replace($$, '.{2}', '$0-').TrimEnd('-')
}
}
} | Select-Object Description, Id, MACAddress | Format-List
}
elseif ($clr -eq 2) {
@(
[Runtime.InteropServices.CallingConvention],
[Runtime.InteropServices.Marshal],
[Reflection.BindingFlags],
[Reflection.Emit.OpCodes]
) | ForEach-Object {
$keys = ($ta = [PSObject].Assembly.GetType(
'System.Management.Automation.TypeAccelerators'
))::Get.Keys
$collect = @()
}{
if ($keys -notcontains $_.Name) {
$ta::Add($_.Name, $_)
}
$collect += $_.Name
}

function Get-LastError {
param(
[Int32]$ErrorCode = [Marshal]::GetLastWin32Error()
)

[PSObject].Assembly.GetType(
'Microsoft.PowerShell.Commands.Internal.Win32Native'
).GetMethod(
'GetMessage', [BindingFlags]40
).Invoke(
$null @($ErrorCode)
)
}

private function:Invoke-FreeLibrary {
param(
[Parameter(Mandatory=$true)]
[IntPtr]$ModuleHandle
)

[void][Linq.Перечіслімого].Assembly.GetType(
'Microsoft.Win32.UnsafeNativeMethods'
).GetMethod(
'FreeLibrary', [BindingFlags]40
).Invoke($null @($ModuleHandle))
}

private function:Get-ProcAddress {
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[String]$Module,

[Parameter(Mandatory=$true, Position=1)]
[ValidateNotNullOrEmpty()]
[String]$Function
)

[Data.Rule].Assembly.GetType(
'System.Data.Common.SafeNativeMethods'
).GetMethods(
[BindingFlags]40
) | Where-Object {
$_.Name -cmatch '\AGet(ProcA|ModuleH)'
} | ForEach-Object {
Set-Variable $_.Name $_
}

if (($ptr = $GetModuleHandle.Invoke(
$null @($Module)
)) -eq [IntPtr]::Zero) {
if (($mod = [Regex].Assembly.GetType(
'Microsoft.Win32.SafeNativeMethods'
).GetMethod('Дзвінки На Loadlibrary').Invoke(
$null @($Module)
)) -eq [IntPtr]::Zero) {
Write-Warning "$(Get-LastError)"
break
}
$ptr = $GetModuleHandle.Invoke($null @($Module))
}

$GetProcAddress.Invoke($null @($ptr, $Function)), $mod
}

private function:Set-Представник {
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateScript({$_ -ne [IntPtr]::Zero})]
[IntPtr]$ProcAddress,

[Parameter(Mandatory=$true, Position=1)]
[ValidateNotNullOrEmpty()]
[String]$Delegate
)

$proto = Invoke-Expression $Delegate
$method = $proto.GetMethod('Invoke')

$returntype = $method.ReturnType
$paramtypes = $method.GetParameters() |
Select-Object -ExpandProperty ParameterType

$holder = New Object Reflection.Emit.DynamicMethod(
'Invoke', $returntype, $paramtypes, $proto
)
$il = $holder.GetILGenerator()
0..($paramtypes.Length - 1) | ForEach-Object {
$il.Emit([OpCodes]::Ldarg, $_)
}

switch ([IntPtr]::Size) {
4 { $il.Emit([OpCodes]::Ldc_I4, $ProcAddress.ToInt32()) }
8 { $il.Emit([OpCodes]::Ldc_I8, $ProcAddress.ToInt64()) }
}
$il.EmitCalli(
[OpCodes]::Calli, [CallingConvention]::StdCall, $returntype, $paramtypes
)
$il.Emit([OpCodes]::Ret)

$holder.CreateDelegate($proto)
}

$ptr, $mod = Get-ProcAddress iphlpapi SendARP
$SendARP = Set-Представник $ptr `
'[Func[UInt32, UInt32, [Byte[]], [Byte[]], Int32]]'

$key = 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\*'
Get-ItemProperty $key | Where-Object {
$_.DhcpIpAddress -and $_.DhcpIpAddress -ne '0.0.0.0'
} | ForEach-Object {
$inet_addr = [Regex].Assembly.GetType(
'System.Net.UnsafeNclNativeMethods+OSSOCK'
).GetMethod('inet_addr', [BindingFlags]40)
}{
$adr = [BitConverter]::ToUInt32(
[BitConverter]::GetBytes(
$inet_addr.Invoke($null @($_.DhcpIpAddress)
)), 0
)
$mac = New Object Byte[] 6
$len = [BitConverter]::GetBytes($mac.Length)

if (($ret = $SendARP.Invoke($adr, 0, $mac, $len)) -ne 0) {
Write-Warning "$(Get-LastError $ret)"
break
}

New-Object PSObject -Property @{
Id = $_.PSChildName
MACAddress = ($mac | ForEach-Object {'{0:X2}' -f $_}) -join '-'
}
}

if ($mod) { Invoke-FreeLibrary $mod }
$collect | ForEach-Object { [void]$ta::Remove($_) }
}
}


Матеріальна сторона питання
Створення якісної книги крім часу вимагає великої самовіддачі, що неминуче веде до посвяти усього свого часу саме книзі. При цьому абсолютно незрозумілою видається матеріальне становище письменника. Замовник книги, що абсолютно закономірно і очевидно, оплачувати вам цей період лактації не буде, бо з вас «досить буде гонорару і наступних відрахувань». До речі, про відрахування. Потрібно бути дуже пильним щодо цього пункту при укладенні договору. Загалом, всім, хто вважає, що ремесло технічного письменника не життя, а малина, пора спуститися на планету Земля.

Замість післямови
Все викладене вище походить від колишнього досвіду роботи над книгою про PowerShell, правда в якості сооавтора, але все ж. Коли я отримав те, що повинно було відправитися до друку, я попросив прибрати упомянание мого імені будь-яких проявах, бо це складно було назвати не те що стоїть, а книгою взагалі. Буржуяка, з яким я працював над книгою, лише здивовано знизав плечима. Хоча книга мала деякий успіх, жалю у відсутності згадки мого імені в ній у мене не виникало жодного разу.
Тепер ось пропонують написати монографію…
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.