Игра не стоит свеч?
А сколько стоят сами свечи? С учетом курса иностранных валют — немало. Проще раскошелиться на дизель-генератор, тем паче что от свеч проку, кроме как от источника освещения, никакого, а от генератора хоть электричество. Впрочем, метафора так себе, давайте снизойдем до привычного IT-шнику стиля.
Написание оригинальной книги требует довольно много времени. Под оригинальностью я разумею не вольный пересказ спецификации или справочного руководства по языку, обычно идущим в комплекте, а то, что как раз за гранью последних. Увы, но подавляющее большинство книг являются именно вольным пересказом, на который, по заявлению самих авторов в предисловии, они тратят от нескольких месяцев до пары-тройки лет. Срок, нужно признать, немалый, ведь за этот период могут произойти определенные изменения в самом языке, в результате чего роль подобных книг умаляется еще больше. Может быть, я и не прав, но на мой личный взгляд чтение документации, сопряженное с различного рода экспериментами, дают намного больше, чем часы, затрачиваемые на вольный пересказ той же документации с повторением примеров из последнего, иными словами хорошим стартом в освоении чего-либо является именно документация и личный опыт, а расширением кругозора — блоги или просто небольшие заметки на различных интернет площадках, полезных среди них, впрочем, также мало.
Подробно — это как?
При написании книги непременно должны существовать рамки, в противном случае можно получить не научную монографию, а что-то из разряда занимательных сведений, которые на Западе обычно принято именовать cookbook. Последний вид книг исключительно вреден, так как, во-первых, формирует шаблонность мышления, во-вторых, ничего кроме фрагментарности в знаниях читающий не получит. Если рассматривать это на каких-то конкретных примерах, то давайте представим себе ситуацию: вы работаете над главой, посвященной сети и все что с ней связано, и хотите продемонстрировать читателю, скажем, как получить свой MAC-адрес, — в зависимости от контекста, решений у задачи очень много, но станете ли вы описывать их все, попутно разъясняя чем один способ лучше другого, а третий — предыдущего? Ответ, полагаю, очевиден. Писатель должен уметь отделять второстепенное от действительно полезных и значимых фактов, при этом давая понять, что приводимый пример кода являет собой лишь один из частных случаев, тем самым стимулируя интерес читателя к собственным экспериментам. Единственное, пожалуй, чем не должен поступаться писатель — упомянания о совместимости версий и ошибках, допущенных самими разработчиками .NET платформы.
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)
)
}
function private:Invoke-FreeLibrary {
param(
[Parameter(Mandatory=$true)]
[IntPtr]$ModuleHandle
)
[void][Linq.Enumerable].Assembly.GetType(
'Microsoft.Win32.UnsafeNativeMethods'
).GetMethod(
'FreeLibrary', [BindingFlags]40
).Invoke($null, @($ModuleHandle))
}
function private: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
}
function private:Set-Delegate {
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-Delegate $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, правда в качестве сооавтора, но все же. Когда я получил то, что должно было отправиться в печать, я попросил убрать упомянание моего имени в каких-либо проявлениях, ибо это сложно было назвать не то что стоящей, а книгой вообще. Буржуин, с которым я работал над книгой, лишь недоуменно пожал плечами. Хотя книга имела некоторый успех, жалости в отсутствии упоминания моего имени в ней у меня не возникало ни разу.
Теперь вот предлагают написать монографию…
Комментарии (11)
GrigoriZakharov
28.07.2016 21:19Прежде всего благодарю Вас за комментарий. Я понял Вашу позицию, но согласиться с Вами все же не могу. Shell с английского оболочка, что уже наводит на мысль о «оберточности», а Power только подчеркивает абстрактность последней. В основе командлетов лежат типы .NET, то есть PowerShell отдельно от .NET существовать не может, именно поэтому «Эффективное использование .NET Framework в PowerShell» звучит практически как «хлеб с булкой» или «булка с хлебом». Что касается передачи книги в издательство… если книга и будет написана, то платной будет только ее печатная версия. По крайней мере это единственное требование с моей стороны к издателю, ибо интерес на постсоветском пространстве к PowerShell не столь велик, как на Западе. Также я планирую покинуть Хабр, так как в планах пара собственных проектов, но сколько бы я ни сигналил адмнистраторам Хабра, блокировать мой аккаунт не спешат.
rbobot
29.07.2016 09:09Первый аккаунт же уже заблокирован? По-моему не стоит и этот переводить в такое же состояние, после блокировки статью нельзя прочитать, кроме как в кеше гугла.
GrigoriZakharov
29.07.2016 12:12Все черновики (исключая некоторые PoC'ы) кодов есть на моем github'е.
gotch
29.07.2016 11:36PowerShell задуман так, что в классах .NET обычно делать нечего — на 60% случаев есть командлеты. Еще в 30% случаев можно работать через WMI. И только в хардкорных случаях надо браться за MSDN.
In computing, a shell is a user interface for access to an operating system's services.
А с эккаунтом вы все же подумайте, ваши статьи могут пригодиться другим. В крайнем случае их всегда можно убрать в черновики. :-(GrigoriZakharov
29.07.2016 12:14Доля правды в Ваших словах есть, однако, в любом случае каждый из нас останется при своем мнении, как в отношении использования типов .NET, так и в плане пользы написанных статей, — написание статей на Хабр отнимает время от исследовательской работы, а некоторые статьи не могут быть опубликованы в принципе из-за их специфичного содержания.
strangewalker
29.07.2016 16:07На собственном примере сталкивался с ситуациями, когда было необходимо автоматизировать те или иные программы, а из доступных средств было только API и кривое-косое описание public классов, так что поддержу gotch, такой вариант книги был бы очень полезен, хоть и для узкого круга.
PS А как найти Ваш гитхаб?
klerik
29.07.2016 15:23пример получения mac на cmd
Пуск-Выполнить cmd написать getmac
Все. Если нужно больше, то getmac -?GrigoriZakharov
29.07.2016 19:12Не совсем понятен Ваш комментарий: желаете ли Вы показать им, что так и не удосужились прочитать пост или это просто желание вставить свои пять копеек? getmac также фигурирует среди примеров в посте.
gotch
03.08.2016 12:07— Доброе утро!
— Что вы хотите этим сказать? Просто желаете мне доброго утра? Или утверждаете, что утро сегодня доброе — неважно, что я о нём думаю? Или имеете в виду, что нынешним утром все должны быть добрыми?
— И то, и другое, и третье И ещё — что в такое дивное утро отлично выкурить трубочку на воздухе. Если у вас есть трубка, присаживайтесь, отведайте моего табачку! Торопиться некуда, целый день впереди!
gotch
Вот что скажу вам, Григорий. )
Читаю ваши посты, и честно говоря, PowerShell-то в них и нет. Net Framework есть, а PowerShell для вас просто обертка. Понимаете, о чем я?
Эту деформацию как раз можно использовать в вашей книге.
Г.Захаров — «Эффективное использование .Net Framework в PowerShell».
Отдавать ли книгу в издательство — вопрос, многие зарубежные авторы продают электронные версии самостоятельно. И в торрентах их не найти. В чем секрет — не знаю.
А если просто «за жизнь», то можно и Пайетта почитать.