Diversión del viernes: un cosquilleo de PowerShell
Esto no reemplaza mi calendario ni mi lista de tareas, pero me permite ver eventos que no quiero perderme directamente desde PowerShell. Mientras trabajaba en esta idea, terminé con un módulo de PowerShell, MyTickle.psm1, que tiene varias funciones para administrar los eventos de cosquillas, como yo los llamo. Desde PowerShell puedo obtener eventos, agregarlos, configurarlos y eliminarlos. Pensé que el módulo sería una buena publicación de Friday Fun porque ciertamente no es un proyecto de alto impacto, pero ofrece algunas ideas sobre cómo crear un módulo y funciones que espero que les resulten útiles.
El módulo por ahora es un solo archivo. Aquí está el archivo y a continuación hablaré del que hablaré.
#requires -version 3.0
# ****************************************************************
# * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED *
# * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF *
# * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, *
# * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. *
# ****************************************************************
#MYTICKLE.PSM1
#Last updated 5/17/2013
#region Define module variables
#This should be the WindowsPowerShell folder under your Documents folder
$profileDir = Split-Path $profile
#the path to the tickle csv file
$TicklePath = Join-Path -Path $profileDir -ChildPath "mytickler.csv"
#the default number of days to display
$TickleDefaultDays = 7
#endregion
#region Define module functions
Function Get-TickleEvent {
<#
.Synopsis
Get Tickle Events
.Description
Get tickle events by id or name. The default behavior is to get all events in
date order. You can specify a range of ID numbers and use wildcards with the
event names. Use the Expired switch to get all tickle events that have already
occurred.
The command will not throw an exception if no matching tickle events are found.
#>
[cmdletbinding(DefaultParameterSetname="ALL")]
Param(
[Parameter(Position=0,ParameterSetName="ID")]
[int[]]$Id,
[Parameter(Position=0,ParameterSetName="Name")]
[string]$Name,
[Parameter(Position=0,ParameterSetName="Expired")]
[switch]$Expired,
[ValidateScript({Test-Path $_} )]
[string]$Path=$TicklePath
)
Write-Verbose "Importing events from $Path"
Switch ($pscmdlet.ParameterSetName) {
"ID" {
Write-Verbose "by ID"
$filter = [scriptblock]::Create("`$_.id -in `$id") }
"Name" {
Write-Verbose "by Name"
$filter = [scriptblock]::Create("`$_.Event -like `$Name") }
"Expired" {
Write-Verbose "by Expiration"
$filter = [scriptblock]::Create("`$_.Date -lt (Get-Date)") }
"All" {
Write-Verbose "All"
$filter = [scriptblock]::Create("`$_ -match '\w+'") }
}
#import CSV and cast properties to correct type
Import-CSV -Path $Path |
Select @{Name="ID";Expression={[int]$_.ID}},
@{Name="Date";Expression={[datetime]$_.Date}},
Event,Comment | where $Filter | Sort date
} #Get-TickleEvent
Function Set-TickleEvent {
<#
.Synopsis
Set a tickle event
.Description
This command will update the settings for a tickle event. You can update the
event name, date or comment. The easiest way to use this is to pipe an tickle
event to this command.
#>
[cmdletbinding(SupportsShouldProcess,DefaultParameterSetName="Inputobject")]
Param(
[Parameter(Position=0,Mandatory,HelpMessage="Enter the tickle event id",ParameterSetName="ID")]
[int]$Id,
[Parameter(Position=1,ValueFromPipeline,ParameterSetname="Inputobject")]
[object]$Inputobject,
[datetime]$Date,
[string]$Event,
[string]$Comment,
[ValidateScript({Test-Path $_} )]
[string]$Path=$TicklePath,
[switch]$Passthru
)
Begin {
write-verbose "Using $($PSCmdlet.ParameterSetName) parameter set"
}
Process {
#if ID only then get event from CSV
Switch ($pscmdlet.ParameterSetName) {
"ID" {
Write-Verbose "Getting tickle event id $ID"
$myevent = Get-TickleEvent -id $id
}
"Inputobject" {
Write-Verbose "Modifying inputobject"
$myevent = $Inputobject
}
} #switch
#verify we have an event to work with
if ($myevent) {
#modify the tickle event object
write-verbose ($myevent | out-string)
if ($Date) {
Write-Verbose "Setting date to $date"
$myevent.date = $Date
}
if ($Event) {
Write-Verbose "Setting event to $event"
$myevent.event = $Event
}
if ($comment) {
Write-verbose "Setting comment to $comment"
$myevent.comment = $comment
}
write-verbose "Revised: $($myevent | out-string)"
#find all lines in the CSV except the matching event
$otherevents = get-content -path $Path | where {$_ -notmatch "^""$($myevent.id)"}
#remove it
$otherevents | Out-File -FilePath $Path -Encoding ascii
#append the revised event to the csv file
$myevent | Export-Csv -Path $Path -Encoding ASCII -Append -NoTypeInformation
if ($passthru) {
$myevent
}
}
else {
Write-Warning "Failed to find a valid tickle event"
}
} #process
} #Set-TickleEvent
Function Add-TickleEvent {
<#
.Synopsis
Add a tickle event
.Description
This command will create a new tickle event. If the CSV file referenced by the
TicklePath variable does not exist, it will be created. You must enter an event
name and date.
#>
[cmdletbinding(SupportsShouldProcess)]
Param (
[Parameter(Position=0,ValueFromPipelineByPropertyName,Mandatory,HelpMessage="Enter the name of the event")]
[string]$Event,
[Parameter(Position=1,ValueFromPipelineByPropertyName,Mandatory,HelpMessage="Enter the datetime for the event")]
[datetime]$Date,
[Parameter(Position=2,ValueFromPipelineByPropertyName)]
[string]$Comment,
[ValidateNotNullorEmpty()]
[string]$Path=$TicklePath,
[switch]$Passthru
)
Begin {
#verify the path and create the file if not found
if (! (Test-Path $Path)) {
Write-Verbose "Creating a new file: $Path"
Try {
'"id","Date","Event","Comment"' |
Out-File -FilePath $Path -Encoding ascii -ErrorAction Stop
}
Catch {
Write-Warning "Failed to create $Path"
Write-Warning $_.Exception.Message
$NoFile = $True
}
}
}
Process {
if ($NoFile) {
Write-Verbose "No CSV file found."
#bail out of the command
Return
}
#get last id and add 1 to it
[int]$last = Import-Csv -Path $Path |
Sort {$_.id -AS [int]} | Select -last 1 -expand id
[int]$id = $last+1
$hash=[ordered]@{
ID = $id
Date = $date
Event = $event
Comment = $comment
}
Write-Verbose "Adding new event"
Write-Verbose ($hash | out-string)
$myevent = [pscustomobject]$hash
$myevent | Export-Csv -Path $Path -Append -Encoding ASCII -NoTypeInformation
if ($passthru) {
$myevent
}
} #process
} #Add-TickleEvent
Function Remove-TickleEvent {
<#
.Synopsis
Remove a tickle event
.Description
Remove one or more events from the tickle file. This will overwrite the current
file so you might want to back it up first with Backup-TickleFile.
.Example
PS C:\> get-ticklevent -expired | remove-tickleevent
#>
[cmdletbinding(SupportsShouldProcess,DefaultParameterSetName="Inputobject")]
Param(
[Parameter(Position=0,Mandatory,HelpMessage="Enter the tickle event id",ParameterSetName="ID")]
[int]$Id,
[Parameter(Position=1,ValueFromPipeline,ParameterSetname="Inputobject")]
[object]$Inputobject,
[ValidateScript({Test-Path $_} )]
[string]$Path=$TicklePath
)
Process {
#if ID only then get event from CSV
Switch ($pscmdlet.ParameterSetName) {
"ID" {
Write-Verbose "Getting tickle event id $ID"
$myevent = Get-TickleEvent -id $id
}
"Inputobject" {
Write-Verbose "Identifying inputobject"
$myevent = $Inputobject
}
} #switch
#verify we have an event to work with
if ($myevent) {
Write-Verbose "Removing event"
Write-Verbose ($myEvent | Out-String)
if ($pscmdlet.ShouldProcess(($myEvent | Out-String))) {
#find all lines in the CSV except the matching event
$otherevents = Get-Content -path $Path | where {$_ -notmatch "^""$($myevent.id)"}
#remove it
$otherevents | Out-File -FilePath $Path -Encoding ascii
}
} #if myevent
} #process
} #Remove-TickleEvent
Function Show-TickleEvent {
<#
.Synopsis
Display Tickle events in the console
.Description
This command gets upcoming tickle events and writes them to the console using
Write-Host. Use this command in your PowerShell profile script.
#>
[cmdletbinding()]
Param(
[Parameter(Position=0)]
[ValidateScript({Test-Path $_})]
[string]$Path=$TicklePath,
[Parameter(Position=1)]
[ValidateScript({$_ -ge 1})]
[int]$Days = $TickleDefaultDays
)
#import events from CSV file
$events = Import-Csv -Path $Path
#get upcoming events within 7 days sorted by date
$upcoming = $events |
where {
#get the timespan between today and the event date
$ts = (New-TimeSpan -Start (Get-Date) -end $_.Date).TotalHours
#find events less than the default days value and greater than 0
Write-Verbose $ts
$ts -le ($Days*24) -AND $ts -gt 0
} |
Add-Member -MemberType ScriptProperty -Name Countdown -value {New-TimeSpan -start (Get-Date) -end $this.date} -PassThru -force|
sort CountDown
if ($upcoming) {
#how wide should the box be?
#get the length of the longest line
$l = 0
foreach ($item in $upcoming) {
#turn countdown into a string without the milliseconds
$count = $item.countdown.ToString()
$time = $count.Substring(0,$count.lastindexof("."))
#add the time as a new property
$item | Add-Member -MemberType Noteproperty -name Time -Value $time
$a = "$($item.event) $($item.Date) [$time]".length
if ($a -gt $l) {$l = $a}
$b = $item.comment.Length
if ($b -gt $l) {$l = $b}
}
[int]$width = $l+5
$header="* Reminders $((Get-Date).ToShortDateString())"
#display events
Write-Host "`r"
Write-host "$($header.padright($width,"*"))" -ForegroundColor Cyan
Write-Host "*$(' '*($width-2))*" -ForegroundColor Cyan
foreach ($event in $upcoming) {
if ($event.countdown.totalhours -le 24) {
$color = "Red"
}
elseif ($event.countdown.totalhours -le 48) {
$color = "Yellow"
}
else {
$color = "Green"
}
#define the message string
$line1 = "* $($event.event) $($event.Date) [$($event.time)]"
if ($event.comment -match "\w+") {
$line2 = "* $($event.Comment)"
$line3 = "*"
}
else {
$line2 = "*"
$line3 = $null
}
$msg = @"
$($line1.padRight($width-1))*
$($line2.padright($width-1))*
"@
if ($line3) {
#if there was a comment add a third line that is blank
$msg+="`n$($line3.padright($width-1))*"
}
Write-Host $msg -ForegroundColor $color
} #foreach
Write-host ("*"*$width) -ForegroundColor Cyan
Write-Host "`r"
} #if upcoming events found
else {
$msg = @"
**********************
* No event reminders *
**********************
"@
Write-host $msg -foregroundcolor Green
}
} #Show-TickleEvent
Function Backup-TickleFile {
<#
.Synopsis
Create a backup of the tickle file
.Description
This command will create a backup copy of the tickle CSV file. The default path
is the same directory as the tickle file. You might want to backup the tickle
file before removing any events.
#>
[cmdletbinding(SupportsShouldProcess)]
Param(
[ValidateScript({Test-Path $_} )]
[string]$Path=$TicklePath,
[ValidateScript({Test-Path $_} )]
[string]$Destination = (Split-Path $TicklePath),
[switch]$Passthru
)
Try {
$ticklefile = Get-Item -Path $path
$backup = Join-Path -path $Destination -ChildPath "$($ticklefile.basename).bak"
Write-Verbose "Copying $path to $backup"
$ticklefile | Copy-Item -Destination $backup -ErrorAction Stop -PassThru:$Passthru
}
Catch {
Write-Warning "Failed to backup file"
Write-Warning $_.exception.message
}
} #Backup-TickleFile
#endregion
#region Define module aliases
Set-Alias -Name gte -value Get-TickleEvent
Set-Alias -name ate -Value Add-TickleEvent
Set-Alias -name rte -Value Remove-TickleEvent
Set-Alias -name ste -Value Set-TickleEvent
Set-Alias -name shte -Value Show-TickleEvent
Set-Alias -name btf -Value Backup-Ticklefile
#endregion
Export-ModuleMember -Function * -Variable TicklePath,TickleDefaultDays -Alias *Debería poder copiar el código del complemento de WordPress y pegarlo en un archivo de script localmente. Puedes llamarlo como quieras, solo recuerda usar una extensión de archivo .psm1. El módulo utiliza algunas funciones de PowerShell 3.0, como tablas hash ordenadas, pero puede revisarlas para que se ejecute en PowerShell 2.0. Básicamente debería funcionar en ambas versiones.
Los eventos se almacenan en un archivo CSV al que hago referencia con una variable de módulo, $TicklePath. El valor predeterminado es un archivo llamado myticckler.csv que estará en su carpeta WindowsPowerShell en Documentos. El módulo también define una variable llamada $TickleDefaultDays, con un valor predeterminado de 7. Esto muestra eventos para aquellos que se encuentran dentro de ese rango. Para usarlo, agregué estas líneas a mi perfil de PowerShell.
import-module c:\scripts\mytickle.psm1
show-tickleevent
El resultado es que cuando inicio una nueva sesión de PowerShell veo algo como esto (el mensaje sobre las actualizaciones de ayuda proviene de otra cosa, así que ignórelo):
Así es como funciona.
La función Show-TickleEvent importa eventos del archivo CSV que sucederán dentro de los próximos 7 días. Cada objeto también obtiene una propiedad adicional que es un objeto de intervalo de tiempo que indica cuánto tiempo queda. Luego, la función analiza la información del evento y construye un "cuadro" alrededor de los eventos.
#how wide should the box be?
#get the length of the longest line
$l = 0
foreach ($item in $upcoming) {
#turn countdown into a string without the milliseconds
$count = $item.countdown.ToString()
$time = $count.Substring(0,$count.lastindexof("."))
#add the time as a new property
$item | Add-Member -MemberType Noteproperty -name Time -Value $time
$a = "$($item.event) $($item.Date) [$time]".length
if ($a -gt $l) {$l = $a}
$b = $item.comment.Length
if ($b -gt $l) {$l = $b}
}
[int]$width = $l+5
$header="* Reminders $((Get-Date).ToShortDateString())"
#display events
Write-Host "`r"
Write-Host "$($header.padright($width,"*"))" -ForegroundColor Cyan
Write-Host "*$(' '*($width-2))*" -ForegroundColor Cyan
#get upcoming events within 7 days sorted by date
$upcoming = $events |
where {
#get the timespan between today and the event date
$ts = (New-TimeSpan -Start (Get-Date) -end $_.Date).TotalHours
#find events less than the default days value and greater than 0
Write-Verbose $ts
$ts -le ($Days*24) -AND $ts -gt 0
} |
Add-Member -MemberType ScriptProperty -Name Countdown -value {New-TimeSpan -start (Get-Date) -end $this.date} -PassThru -force |
sort CountDownEstablezco un color de primer plano dependiendo de cuán inminente sea el evento y luego escribo cada evento en la consola, envuelto en mi borde.
#define the message string
$line1 = "* $($event.event) $($event.Date) [$($event.time)]"
if ($event.comment -match "\w+") {
$line2 = "* $($event.Comment)"
$line3 = "*"
}
else {
$line2 = "*"
$line3 = $null
}
$msg = @"
$($line1.padRight($width-1))*
$($line2.padright($width-1))*
"@
if ($line3) {
#if there was a comment add a third line that is blank
$msg+="`n$($line3.padright($width-1))*"
}
Write-Host $msg -ForegroundColor $colorUtilicé Write-Host a propósito para poder codificar con colores los eventos y porque no quería que el perfil escribiera nada en la canalización. Debido a que el módulo se carga al inicio de mi sesión de PowerShell, siempre puedo ejecutar Show-TickleEvent y especificar el evento una cantidad diferente de días. Si quiero objetos, puedo usar la función Get-TickleEvent que importará los eventos csv según criterios como ID o nombre. La función utiliza conjuntos de parámetros y creo un bloque de secuencia de comandos de filtro según el conjunto de parámetros.
Switch ($pscmdlet.ParameterSetName) {
"ID" {
Write-Verbose "by ID"
$filter = [scriptblock]::Create("`$_.id -in `$id") }
"Name" {
Write-Verbose "by Name"
$filter = [scriptblock]::Create("`$_.Event -like `$Name") }
"Expired" {
Write-Verbose "by Expiration"
$filter = [scriptblock]::Create("`$_.Date -lt (Get-Date)") }
"All" {
Write-Verbose "All"
$filter = [scriptblock]::Create("`$_ -match '\w+'") }
}Cuando importo el archivo CSV, agrego tipos a las propiedades porque de lo contrario todo sería una cadena y luego canalizo cada objeto a mi filtro.
#import CSV and cast properties to correct type
Import-CSV -Path $Path |
Select @{Name="ID";Expression={[int]$_.ID}},
@{Name="Date";Expression={[datetime]$_.Date}},
Event,Comment | where $Filter | Sort DateEstos objetos resultan útiles porque se pueden canalizar a Set-TickleEvent para modificar valores como el nombre del evento, la fecha o el comentario. O puedo canalizar a Remove-TickleEvent para eliminar entradas. Básicamente, el proceso de eliminación encuentra todas las líneas del archivo CSV que no comienzan con la identificación correcta y crea un nuevo archivo con el mismo nombre.
if ($pscmdlet.ShouldProcess(($myEvent | Out-String))) {
#find all lines in the CSV except the matching event
$otherevents = Get-Content -path $Path | where {$_ -notmatch "^""$($myevent.id)"}
#remove it
$otherevents | Out-File -FilePath $Path -Encoding ascii
}Finalmente, después de borrar accidentalmente los archivos de eventos, agregué una función de copia de seguridad simple que copia el archivo CSV en el mismo directorio pero con una extensión de archivo .BAK. Podría especificar una ruta alternativa, pero de forma predeterminada es la carpeta WindowsPowerShell.
Con suerte, no volveré a perderme eventos importantes, suponiendo, por supuesto, que los agregue a mi archivo de recordatorios. Te dejaré jugar con Add-TickleEvent para ver cómo funciona o siempre puedes modificar el archivo CSV con el Bloc de notas.
Si realmente usas esto, espero que me lo hagas saber.

