Cuma Eğlencesi: Geliştirilmiş PowerShell Uyuklaması
Geçen hafta PowerShell ile biraz kestirmeye ilişkin yazımda biraz eğlendim. Twitter'da harika geri bildirimler ve blogda yeni yorumlar aldım. İlk çabam, işin kesinlikle yapılmasını sağlayan nispeten basit bir PowerShell betiğiydi. Ancak senaryoyu genişletip geliştirebileceğim birkaç alan vardı ve bunlar harika öğretim yardımcıları olacaktı. Ben de yaptım.
İşlev, Github'da bulabileceğiniz bir komut dosyasında tanımlanmıştır.
PowerShellNap.ps1:
#requires -Version 5.1
Function Start-PSNap {
<#
.SYNOPSIS
Start a PowerShell napping session.
.DESCRIPTION
Use this command to start a short napping session. The command will alert you when you nap is up with a chime and a message. You have an option of displaying the message on the screen or having it spoken.
.PARAMETER Minutes
The number of minutes for your nap. This command has aliases of: nap and time
.PARAMETER ProgressBar
Indicate if you want to show a progress bar which includes a number of messages.
.PARAMETER Message
The text of the message to be displayed or spoken at the end of your nap.
.PARAMETER Voice
Specify the name of the installed voice to use. More most US desktops this will be David, Zira and perhaps Hazel. If you use this parameter the message will not be written to host. This dynamic parameter is only available in Windows PowerShell.
.PARAMETER Rate
The voice speaking rate. Enter a value between -5 and 5. This dynamic parameter is only available in Windows PowerShell.
.EXAMPLE
PS C:\> Start-PSNap 10 -ProgressBar -message "Get back to work you lazy bum!"
Start a 10 minute nap with the progress bar and display the given message in the console host.
.EXAMPLE
PS C:\> Start-PSNap 15 -message "Wake up you fool and get back to work." -voice "Microsoft Zira Desktop"
Start a 15 minute nap and use the computer voice Zira to speak the wake up message.
.NOTES
Learn more about PowerShell: http://jdhitsolutions.com/blog/essential-powershell-resources/
.LINK
Start-Sleep
.INPUTS
none
.OUTPUTS
none
#>
[cmdletbinding(DefaultParameterSetName = "host")]
[alias("psnap")]
Param (
[Parameter(Position = 0)]
[Alias("nap", "time")]
[ValidateRange(1, 30)]
[int]$Minutes = 1,
[switch]$ProgressBar,
[ValidateNotNullorEmpty()]
[string]$Message = "Get back to work sleepy head!"
)
DynamicParam {
#create a dynamic parameter if running Windows PowerShell
if ($psedition -eq 'Desktop' ) {
#define a parameter attribute object
$attributes = New-Object -TypeName System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "speech"
$attributes.HelpMessage = "Select an installed voice"
#add a dynamic validation set
#get voices
try {
Add-Type -AssemblyName System.speech -ErrorAction Stop
}
Catch {
Throw $_
}
[string[]]$installed = [System.Speech.Synthesis.SpeechSynthesizer]::new().GetInstalledVoices().voiceinfo.Name
# [regex]$rx= "Microsoft\s+(?\w+)\s+"
#build a list of voices assuming the voice name is something like Microsoft David Desktop
# $choices = (($rx.Matches($installed)).foreach({$_.groups["name"].value})) -join ","
$validate = [System.Management.Automation.ValidateSetAttribute]::new($installed)
#define a collection for attributes
$attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($attributes)
$attributeCollection.Add($validate)
#define the dynamic param
$dynParam1 = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("Voice", [string], $attributeCollection)
#set a default voice
$dynParam1.Value = $installed[0]
#create array of dynamic parameters
$paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add("Voice", $dynParam1)
#create the Rate parameter
<#
[Parameter(ParameterSetName = "voice")]
[ValidateRange(-5, 5)]
[int]$Rate = -1
#>
#re-use the attributes variable
$attributes.HelpMessage = "Select the speaking rate."
$validate = [System.Management.Automation.ValidateRangeAttribute]::new(-5,5)
$attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($attributes)
$attributeCollection.Add($validate)
$dynParam2 = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("Rate", [int32], $attributeCollection)
$dynParam2.value = 0
#create array of dynamic parameters
$paramDictionary.Add("Rate", $dynParam2)
#use the array
return $paramDictionary
}
}
Begin {
$wake = (Get-Date).AddMinutes($Minutes)
$remainingSeconds = $minutes * 60
#an array of status messages if using a progress bar
$ops = @(
"I'm solving a PowerShell problem",
"I'm chasing cmdlets",
"Brilliance at work",
"Re-initializing my pipeline",
"Go away",
"I'm checking eyelid integrity",
"It can wait...",
"Don't you dare!",
"Spawning a new runspace",
"I'm multitasking",
"Nothing is that important",
"Unless you have a glass of [beer|scotch|wine|bourbon] for me, go away",
"I'm testing the new PSNap provider",
"I need this",
"I'm downloading my new matrix",
"Resource recyling in progress",
"Life is but a dream",
"Garbage collection initiated",
"Nudge me if I'm snoring",
"I took the red pill",
"Synaptic synch in progress",
"Neural network rebooting",
"Reformatting synapses",
"There's no place like home. There's no place like home.",
"If you can read this you should go away",
"$($env:username) has left the building"
)
#hashtable of parameter values to splat to Write-Progress
$progHash = @{
Activity = "Ssssshhh..."
Status = $ops[0]
SecondsRemaining = $remainingSeconds
}
Clear-Host
} #begin
Process {
#loop until the time is >= the wake up time
do {
if ($ProgressBar ) {
Write-Progress @proghash
#tick down $remainingseconds
$proghash.SecondsRemaining = $remainingSeconds--
#pick a new random status if remaining seconds is divisible by 10
if ($remainingSeconds / 10 -is [int]) {
$proghash.status = $ops | Get-Random
}
} #if
else {
Clear-Host
Write-Host "Ssshhhh...." -ForegroundColor green
#trim off the milliseconds
Write-Host ($wake - (Get-Date)).ToString().Substring(0, 8) -NoNewline
} #else
Start-Sleep -Seconds 1
} Until ( (Get-Date) -ge $wake )
} #process
End {
#Play wake up music
[console]::Beep(392, 950)
[console]::Beep((329.6*2), 950)
[console]::Beep(523.2, 950)
If ($PSBoundParameters.ContainsKey("Voice")) {
#pause a moment after the wakeup music
Start-Sleep -Milliseconds 700
Add-Type -AssemblyName System.speech
$speech = New-Object System.Speech.Synthesis.SpeechSynthesizer
$speech.SelectVoice($psboundparameters.Item("Voice"))
$speech.Rate = $Rate
$speech.SpeakAsync($message) | Out-Null
#write a blank line to get a new prompt
Write-Host "`n"
}
else {
Write-Host "`n$Message" -ForegroundColor Yellow
}
} #end
} #end function Yaptığım bazı değişikliklere bakalım. Öncelikle bunu yoruma dayalı yardımla tamamlanmış bir fonksiyona dönüştürdüm. Komutu kullanılabilir hale getirmek için betik dosyasını PowerShell oturumunuza veya profil betiğinize nokta kaynağı olarak eklemeniz gerekir.
Hemen hemen her seçeneği bir parametre yaptım ve birkaç parametre takma adı da ekledim. Her ne kadar parametre adını kullanmanıza gerek kalmasın diye Dakika parametresini konumsal yapsam da -Nap veya -Time kullanabilirsiniz. Uyandırma mesajını da bir parametre haline getirdiğimi fark edeceksiniz. Kendi varsayılan değerinizi ayarlamaktan çekinmeyin. Aksi takdirde farklı zamanlarda farklı bir mesaj ayarlayabilirsiniz.
Ayrıca eğer kestiriyorsanız birisinin yine de masanıza uğrayabileceğini fark ettim. Bu yüzden Yazma İlerlemesini kullanarak bir ilerleme çubuğu görüntüleme seçeneği ekledim. Bu, olması gereken sevgiyi alamayan bir cmdlet'tir.
Bir dizi mesaj tanımladım:
#an array of status messages if using a progress bar
$ops = "I'm solving a PowerShell problem","I'm chasing cmdlets",
"Brilliance at work","Re-initializing my pipeline",
"Go away","I'm checking eyelid integrity","It can wait...",
"Don't you dare!","Spawning a new runspace","I'm multitasking",
"Nothing is that important","Unless you have beer for me, go away",
"I'm testing the new PSNap provider","I need this",
"I'm downloading my new matrix","Resource recyling in progress",
"Synaptic synch in progress","Neural network rebooting",
"If you can read this you shouldn't be here",
"$($env:username) has left the building"Mesajlar Yazma İlerlemesi için Durum özelliği olarak kullanılacaktır. Yazma İlerlemesini kullanırken uyarmak için bir parametre karma tablosu kullanmayı seviyorum.
$progHash = @{
Activity = "Ssssshhh..."
Status = $ops[0]
SecondsRemaining = $remainingSeconds
}İlerleme çubuğunu kullanırsam kalan saniyeler kullanılarak görüntülenir.
if ($ProgressBar ) {
Write-Progress @proghash
#tick down $remainingseconds
$proghash.SecondsRemaining = $remainingSeconds--
#pick a new random status if remaining seconds is divisible by 10
if ($remainingSeconds/10 -is [int]) {
$proghash.status = $ops | Get-Random
}
} #if
Ve her 10 saniyede bir durumu rastgele seçilen başka bir mesaja ayarlıyorum. Sonuç şunun gibi bir şeydir:
Bir öneriye göre yaptığım son büyük değişiklik, Windows sesinin uyandırma mesajını "söylemesini" sağlamak için metinden konuşmaya özelliğini kullanmaktı. ABD'de büyük olasılıkla David veya Zira olacak bir ses adı belirtmeniz için bir parametre ekledim. Adları bilmiyorsanız 'foo' gibi sahte bir değer belirtebilirsiniz; işlev mevcut adları görüntüler. Bu işe yarıyor çünkü Ses parametresine bir doğrulama komut dosyası ekledim.
[ValidateScript({
#get voices
Add-Type -AssemblyName System.speech
$installed = [System.Speech.Synthesis.SpeechSynthesizer]::new().GetInstalledVoices().voiceinfo.Name
if ($installed -match $_ ) {
$True
}
else {
[regex]$rx= "Microsoft\s+(?\w+)\s+"
#build a list of voices assuming the voice name is something like Microsoft David Desktop
$choices = (($rx.Matches($installed)).foreach({$_.groups["name"].value})) -join ","
Throw "Can't find an installed voice for $_. Possible values are: $choices"
}
})]
[Parameter(ParameterSetName = "voice")]
[string]$Voice,
Bu muhtemelen çoğu doğrulama komut dosyasından biraz daha karmaşıktır. Ana çıkarım, eğer bir doğrulama komut dosyası kullanırsanız, Doğru veya Yanlış döndürmesi veya benim burada yaptığım gibi bir istisna atması gerektiğidir. Ama işe yarıyor.
Bir ses seçeneği ekleyerek, işlevin mesajı Write-Host kullanarak görüntüleyebileceğine veya konuşabileceğine karar verdim. Her iki durumda da zil sesi duyulur.
If ($Voice) {
Add-Type -AssemblyName System.speech
$speech = New-Object System.Speech.Synthesis.SpeechSynthesizer
#find the matching voice object
$selected = [System.Speech.Synthesis.SpeechSynthesizer]::new().GetInstalledVoices().voiceinfo.Name | where {$_ -match $voice}
$speech.SelectVoice($selected)
$speech.Rate = $Rate
$speech.SpeakAsync($message) | Out-Null
#write a blank line to get a new prompt
Write-Host "`n"
}
else {
Write-Host "`n$Message" -ForegroundColor Yellow
}
Bunun anlamı, parametre setleriyle yaptığım parametrelerin ayrımını yapmam gerektiğiydi. Varsayılanı cmdletbinding özelliğinde belirttim.
[cmdletbinding(DefaultParameterSetName = "host")]
Daha sonra her parametre için bir parametre seti adı belirtmem gerekiyordu. Parametre seti adını belirtmezseniz parametre tüm setlere ait olacaktır. Veya benim yaptığımı yapıp açık konuşabilirsiniz. Eğer bunu doğru şekilde yaparsanız, yardıma yansıtılmalıdır.
Bu komutu kullanmanın 2 yolu olduğunu görebilirsiniz. Bir kopya alıp yeni eklemeleri denemenize izin vereceğim.
Kesinlikle bu üretim odaklı bir senaryo değil ama umarım farklı kodlama teknikleri ve cmdlet'lerden bazı ilginç örnekler sunar.
Her zaman olduğu gibi yorumlar içtenlikle karşılandı.
Eğlence!