PowerShell: за гранью. Часть шестая

в 11:12, , рубрики: powershell, powershell4, Программирование, Яндекс API, метки:

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

Если проводить аналогии, профили PowerShell — те же конфигурационные файлы терминалов *nix, основное назначение которых хранить пользовательские настройки. Более подродно о профилях можно почитать во встроенном руководстве:

PS C:> man about_Profiles

Совершенно очевидно, что пихать в профиль все что ни поподя не следует — чем больше вес файла, тем менее скорость загрузки хоста, если конечно не был указан параметр /noprofile при вызове последнего. Обычно профили применяются для упреждения неоднозначностей (о них чуть позже), настройки окружения и определения часто используемых функций. Попробуем пояснить это на примере.

PS C:> ni -Type File -Path $Profile -Force
PS C:> vim $Profile #vim - сугубо личное предпочтение

Было бы неплохо иметь постоянно доступ к ускорителям типов.

if (($ta = [PSObject].Assembly.GetType(
  'System.Management.Automation.TypeAccelerators'
))::Get.Keys -notcontains 'accelerators') {
  $ta::Add('accelerators', $ta)
}

Для того, чтобы изменения вступили в силу, требуется перезапустить хост.

PS C:> ii (ls $env:allusersprofile -r -ea 0).Where({$_.Name -match 'shell.lnk'}).FullName;kill $pid
...
PS C:> [accelerators]::Get

Key                    Value
---                    -----
Alias                  System.Management.Automation.AliasAttribute
AllowEmptyCollection   System.Management.Automation.AllowEmptyCollectionAttribute
AllowEmptyString       System.Management.Automation.AllowEmptyStringAttribute
AllowNull              System.Management.Automation.AllowNullAttribute
array                  System.Array
bool                   System.Boolean
byte                   System.Byte
char                   System.Char
CmdletBinding          System.Management.Automation.CmdletBindingAttribute
datetime               System.DateTime
...

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

PS C:> gc env:allusersprofile
C:ProgramData
PS C:> $old = gc env:allusersprofile
PS C:> sc env:allusersprofile C:
PS C:> gc env:allusersprofile
C:
PS C:> sc env:allusersprofile $old
PS C:> gc env:allusersprofile
C:ProgramData

Иными словами перезапуск хоста способом выше без предварительной проверки переменной allusersprofile — не самая лучшая идея, и, если в том возникает необходимость, лучше использовать переменные окружения определенные в Environment+SpecialFolder или просто предопределить некоторые переменные env: в виде констант.

Так как Корзина используется некоторыми как пункт промежуточного хранения файлов, почему бы не упростить себе задачу и в этом случае?!

Set-Content function:trash {
  param(
    [Parameter(Mandatory=$true)]
    [ValidateScript({Test-Path $_})]
    [String]$Path
  )

  (New-Object -ComObject Shell.Application).NameSpace(0xA).MoveHere(
    (Convert-Path $Path)
  )
}

В итоге помещать файлы в Корзину станет проще:

PS C:> trash E:docfoo

Требуется возможностью перевода незнакомых слов? Денег на покупку дорогостоящих приложений у нас нет, поэтому будем использовать онлайн-переводчик, а точнее — Яндекс.Перевод, благо у того вменяемое API, да и качество переводов неплохое. Устанавливаем API-ключ.

PS C:> $key = '...' #API-ключ
PS C:> $bin = "$([Environment]::GetFolderPath('UserProfile'))yatrans.bin"
PS C:> Add-Type -AssemblyName System.Security
PS C:> [IO.File]::WriteAllBytes(
>> $bin,
>> [Security.Cryptography.ProtectedData]::Protect(
>> [Text.Encoding]::Unicode.GetBytes($key),
>> $null,
>> [Security.Cryptography.DataProtectionScope]::CurrentUser
>> ))
>>
PS C:> attrib +h $bin

Ключ установили, теперь можно приступать к чтению документации API. Если же совсем невтерпеж, то в самом простом варианте, функция перевода может выглядеть так.

function Get-Translation {
  param(
    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [String]$Data
  )
  
  begin {
    Add-Type -AssemblyName System.Security
    # декодирование ключа
    if (Test-Path ($key = "$([Environment]::GetFolderPath('UserProfile'))yatrans.bin")) {
      $key = [Text.Encoding]::Unicode.GetString(
        [Security.Cryptography.ProtectedData]::Unprotect(
          [IO.File]::ReadAllBytes($key),
          $null,
          [Security.Cryptography.DataProtectionScope]::CurrentUser
        )
      )
    }
    else {
      throw 'API-ключ Яндекс.Перевод не найден.'
    }
    # url-root
    $url = 'https://translate.yandex.net/api/v1.5/tr/'
    # определение языка оригинала
    $detect = "$($url)detect?key=$key&text="
    # перевод
    $transl = "$($url)translate?key=$key&text=%t&lang=%l-ru&format=plain"
    # user agent
    $usr = 'Mozilla/5.0 (Windows NT 6.3; rv:37.0.1) Gecko/20100101 Firefox/37.0.1'
  }
  process {
    # для перевода текстовых файлов
    $Data = if (Test-Path $Data) { gc $Data } else { $Data }
    $res = [xml](wget "$detect$Data" -DisableKeepAlive -UseBasicParsing -UserAgent $usr).Content
    if ($res.DetectedLang.code -ne 200) {
      throw 'Невозможно определить язык.'
    }
    
    $transl = $transl -replace '%t', $Data
    $transl = $transl -replace '%l', $res.DetectedLang.lang
    $res = [xml](wget $transl -DisableKeepAlive -UseBasicParsing -UserAgent $usr).Content
    if ($res.Translation.code -ne 200) {
      throw 'Невозможно перевести текст.'
    }
    $res.Translation.text
  }
}

Функция в действии:

PS C:> Get-Translation 'Die Zeit ist auf!'
- Время!
PS C:> vim foo
Hey, teacher! Leave this kids alone!
...
PS C:> Get-Translation foo
Эй, учитель! Оставить детей в покое!
PS C:> ri foo
PS C:>

К слову, Яндекс предоставляет возможность узнать свой публичный IP — также может сгодится в хозяйстве:

$par = @{
  Uri = 'http://ipv4.internet.yandex.ru/internet/api/v0/ip'
  DisableKeepAlive = $true
  UseBasicParsing = $true
  UserAgent = 'Mozilla/5.0 (Windows NT 6.3; rv:37.0.1) Gecko/20100101 Firefox/37.0.1'
}

(wget @par).Content -replace [Char]34, ''

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

Автор: gregzakharov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js