From 0be252ec0e05425bdd33f88dec481d65f67143c2 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Tue, 19 Jul 2016 12:19:56 -0700 Subject: [PATCH 1/9] Use class instead of PSObject for Get/Remove-CronJob --- demos/modules/CronTab/CronTab.psm1 | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/demos/modules/CronTab/CronTab.psm1 b/demos/modules/CronTab/CronTab.psm1 index f8bd2e7c4..0301636b2 100644 --- a/demos/modules/CronTab/CronTab.psm1 +++ b/demos/modules/CronTab/CronTab.psm1 @@ -1,14 +1,13 @@ $crontabcmd = "/usr/bin/crontab" -#TODO fix after https://github.com/PowerShell/PowerShell/issues/932 is fixed -#class CronJob { -# [string] $Minute -# [string] $Hour -# [string] $DayOfMonth -# [string] $Month -# [string] $DayOfWeek -# [string] $Command -#} +class CronJob { + [string] $Minute + [string] $Hour + [string] $DayOfMonth + [string] $Month + [string] $DayOfWeek + [string] $Command +} # Internal helper functions @@ -27,8 +26,7 @@ function Get-CronTab ([String] $user) { function ConvertTo-CronJob ([String] $crontab) { $split = $crontab.split(" ", 6) - $cronjob = New-Object -TypeName PSObject -Property @{ #TODO: change to CronJob type - PSTypeName="CronJob"; + $cronjob = [CronJob]@{ Minute = $split[0]; Hour = $split[1]; DayOfMonth= $split[2]; @@ -82,7 +80,7 @@ function Remove-CronJob { [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")] param ( [Alias("u")][Parameter(Mandatory=$false)][String] $UserName, - [Alias("j")][Parameter(Mandatory=$true,ValueFromPipeline=$true)][PSTypeName("CronJob")] $Job #TODO use CronJob type + [Alias("j")][Parameter(Mandatory=$true,ValueFromPipeline=$true)][CronJob] $Job ) process { @@ -173,7 +171,7 @@ function Get-CronJob { Optional parameter to specify a specific user's cron table #> [CmdletBinding()] - [OutputType([PSObject])] + [OutputType([CronJob])] param ( [Alias("u")][Parameter(Mandatory=$false)][String] $UserName ) From ab5bda18e07f56f95b3ab53a7bdcd99033a878bd Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Wed, 20 Jul 2016 13:49:44 -0700 Subject: [PATCH 2/9] Fix removing all lines from crontab --- demos/crontab.ps1 | 23 ++++++++++++++++++++--- demos/modules/CronTab/CronTab.psm1 | 9 ++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/demos/crontab.ps1 b/demos/crontab.ps1 index d90e2dbdb..85bd63139 100644 --- a/demos/crontab.ps1 +++ b/demos/crontab.ps1 @@ -1,9 +1,26 @@ -# Use the crontab module available at ./modules/CronTab + +if (!($env:PSMODULEPATH -split ';' | Where-Object { $_.StartsWith($PSScriptRoot) })) +{ + $env:PSMODULEPATH += ";$PSScriptRoot/modules" +} + +Import-Module CronTab # Get the existing cron jobs +Get-CronJob + +# New cron job to clean out tmp every day at 1am +New-CronJob -Command 'rm -rf /tmp/*' -Hour 1 + +# New cron job to start a build +New-CronJob -Command 'powershell -c "cd ~/src/PowerShell; ipmo ./build.psm1; Start-PSBuild"' -Hour 2 -DayOfWeek 1-5 # Sort them by data - -# Create a new one using a DateTime object +Get-CronJob | Sort-Object Command # Show in bash that the new cron job exists +crontab -l + +# Remove a cron job +Get-CronJob | Where-Object { $_.Command -match '^powershell.*' } | Remove-CronJob + diff --git a/demos/modules/CronTab/CronTab.psm1 b/demos/modules/CronTab/CronTab.psm1 index 0301636b2..61cf8ecf7 100644 --- a/demos/modules/CronTab/CronTab.psm1 +++ b/demos/modules/CronTab/CronTab.psm1 @@ -1,3 +1,6 @@ + +using namespace System.Collections.Generic + $crontabcmd = "/usr/bin/crontab" class CronJob { @@ -15,7 +18,7 @@ function Get-CronTab ([String] $user) { $crontab = Invoke-CronTab -user $user -arguments "-l" -noThrow if ($crontab -is [System.Management.Automation.ErrorRecord]) { if ($crontab.Exception.Message.StartsWith("no crontab for ")) { - $crontab = $null + $crontab = @() } else { throw $crontab.Exception @@ -85,7 +88,7 @@ function Remove-CronJob { process { [string[]] $crontab = Get-CronTab -user $UserName - [string[]] $newcrontab = $null + $newcrontab = [List[string]]::new() $found = $false foreach ($line in $crontab) { @@ -93,7 +96,7 @@ function Remove-CronJob { if ((Compare-object $cronjob.psobject.properties $Job.psobject.properties) -eq $null) { $found = $true } else { - $newcrontab += $line + $newcrontab.Add($line) } } From ec1369f1dcbee63c581c28fef13feeddf8517446 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 08:44:36 -0700 Subject: [PATCH 3/9] Add argument completion for -UserName --- demos/modules/CronTab/CronTab.psd1 | Bin 3520 -> 3556 bytes demos/modules/CronTab/CronTab.psm1 | 52 ++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/demos/modules/CronTab/CronTab.psd1 b/demos/modules/CronTab/CronTab.psd1 index 23d9d95f051b15feb3b1c0ca41ca273156cd41b4..5a73eb59cf2eb65bbaca9301c1427f3eb49f9234 100644 GIT binary patch delta 30 mcmX>g{X}}h7WT9hCIAHL diff --git a/demos/modules/CronTab/CronTab.psm1 b/demos/modules/CronTab/CronTab.psm1 index 61cf8ecf7..b49201ce9 100644 --- a/demos/modules/CronTab/CronTab.psm1 +++ b/demos/modules/CronTab/CronTab.psm1 @@ -1,5 +1,6 @@ using namespace System.Collections.Generic +using namespace System.Management.Automation $crontabcmd = "/usr/bin/crontab" @@ -16,7 +17,7 @@ class CronJob { function Get-CronTab ([String] $user) { $crontab = Invoke-CronTab -user $user -arguments "-l" -noThrow - if ($crontab -is [System.Management.Automation.ErrorRecord]) { + if ($crontab -is [ErrorRecord]) { if ($crontab.Exception.Message.StartsWith("no crontab for ")) { $crontab = @() } @@ -82,8 +83,16 @@ function Remove-CronJob { #> [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")] param ( - [Alias("u")][Parameter(Mandatory=$false)][String] $UserName, - [Alias("j")][Parameter(Mandatory=$true,ValueFromPipeline=$true)][CronJob] $Job + [ArgumentCompleter( { $wordToComplete = $args[2]; Get-CronTabUser | Where-Object { $_ -like "$wordToComplete*" } | Sort-Object } )] + [Alias("u")] + [Parameter(Mandatory=$false)] + [String] + $UserName, + + [Alias("j")] + [Parameter(Mandatory=$true,ValueFromPipeline=$true)] + [CronJob] + $Job ) process { @@ -141,7 +150,12 @@ function New-CronJob { #> [CmdletBinding()] param ( - [Alias("u")][Parameter(Mandatory=$false)][String] $UserName, + [ArgumentCompleter( { $wordToComplete = $args[2]; Get-CronTabUser | Where-Object { $_ -like "$wordToComplete*" } | Sort-Object } )] + [Alias("u")] + [Parameter(Mandatory=$false)] + [String] + $UserName, + [Alias("mi")][Parameter(Position=1)][String[]] $Minute = "*", [Alias("h")][Parameter(Position=2)][String[]] $Hour = "*", [Alias("dm")][Parameter(Position=3)][String[]] $DayOfMonth = "*", @@ -188,3 +202,33 @@ function Get-CronJob { } } } + +function Get-CronTabUser { +<# +.SYNOPSIS + Returns the users allowed to use crontab +#> + [CmdletBinding()] + [OutputType([String])] + param() + + $allow = '/etc/cron.allow' + if (Test-Path $allow) + { + Get-Content $allow + } + else + { + $users = Get-Content /etc/passwd | ForEach-Object { ($_ -split ':')[0] } + $deny = '/etc/cron.deny' + if (Test-Path $deny) + { + $denyUsers = Get-Content $deny + $users | Where-Object { $denyUsers -notcontains $_ } + } + else + { + $users + } + } +} From 3dafae5357926de040ee2f3e8d9647af6d0a1bc6 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 08:50:11 -0700 Subject: [PATCH 4/9] Move crontab demo to it's own directory Also change CronTab.psd1 encoding to ASCII --- .../CronTab/CronTab.ps1xml | 0 demos/crontab/CronTab/CronTab.psd1 | 61 ++++++++++++++++++ .../{modules => crontab}/CronTab/CronTab.psm1 | 0 demos/{ => crontab}/crontab.ps1 | 7 +- demos/modules/CronTab/CronTab.psd1 | Bin 3556 -> 0 bytes 5 files changed, 62 insertions(+), 6 deletions(-) rename demos/{modules => crontab}/CronTab/CronTab.ps1xml (100%) create mode 100755 demos/crontab/CronTab/CronTab.psd1 rename demos/{modules => crontab}/CronTab/CronTab.psm1 (100%) rename demos/{ => crontab}/crontab.ps1 (75%) delete mode 100644 demos/modules/CronTab/CronTab.psd1 diff --git a/demos/modules/CronTab/CronTab.ps1xml b/demos/crontab/CronTab/CronTab.ps1xml similarity index 100% rename from demos/modules/CronTab/CronTab.ps1xml rename to demos/crontab/CronTab/CronTab.ps1xml diff --git a/demos/crontab/CronTab/CronTab.psd1 b/demos/crontab/CronTab/CronTab.psd1 new file mode 100755 index 000000000..15c279cbb --- /dev/null +++ b/demos/crontab/CronTab/CronTab.psd1 @@ -0,0 +1,61 @@ +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'CronTab.psm1' + +# Version number of this module. +ModuleVersion = '0.1.0.0' + +# Supported PSEditions +CompatiblePSEditions = @('Linux') + +# ID used to uniquely identify this module +GUID = '508bb97f-de2e-482e-aae2-01caec0be8c7' + +# Author of this module +Author = 'Microsoft' + +# Company or vendor of this module +CompanyName = 'Unknown' + +# Copyright statement for this module +Copyright = '(c) 2016 Microsoft. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'Sample module for managing CronTab' + +# Format files (.ps1xml) to be loaded when importing this module +FormatsToProcess = 'CronTab.ps1xml' + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = 'New-CronJob','Remove-CronJob','Get-CronJob','Get-CronTabUser' + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +} + diff --git a/demos/modules/CronTab/CronTab.psm1 b/demos/crontab/CronTab/CronTab.psm1 similarity index 100% rename from demos/modules/CronTab/CronTab.psm1 rename to demos/crontab/CronTab/CronTab.psm1 diff --git a/demos/crontab.ps1 b/demos/crontab/crontab.ps1 similarity index 75% rename from demos/crontab.ps1 rename to demos/crontab/crontab.ps1 index 85bd63139..9e17acaed 100644 --- a/demos/crontab.ps1 +++ b/demos/crontab/crontab.ps1 @@ -1,10 +1,5 @@ -if (!($env:PSMODULEPATH -split ';' | Where-Object { $_.StartsWith($PSScriptRoot) })) -{ - $env:PSMODULEPATH += ";$PSScriptRoot/modules" -} - -Import-Module CronTab +Import-Module $PSScriptRoot/CronTab/CronTab.psd1 # Get the existing cron jobs Get-CronJob diff --git a/demos/modules/CronTab/CronTab.psd1 b/demos/modules/CronTab/CronTab.psd1 deleted file mode 100644 index 5a73eb59cf2eb65bbaca9301c1427f3eb49f9234..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3556 zcmbuCUr!TJ5XI-&#P6_)4`Ko;Mg<>CAb^TNVg!6|p@mhtTiYTU!&g_&?+%x{ciTmc zA$9-Uxie?Z%$&Ra`nh30?5^Djzk62LfgRh#N*miuPa~`C#4haI270QsQdnQBmGyN0 zea>R2HMi~ARDZR+m9m{_|B0+G^)$0H-SKZJ3u5%-ue70Trh5M{VELfEwVvMT--O_$ z?C;x_V%OSpsGWV;kF{@Pt6FJh@=^XKS}B8VA^Qti^}}0o8MaWQIF6thx$VnwJXY*g z{kT4EML$rqvHsvJX8&I7?kF2La8%QPcuQ6z*}_vPgfO_swlLyu*pg!H$&!q|>Ha~M z_pWWr>Oz@2=QDZd8IHf`I*@-VR|?5MZ;3e!Z<;kpY{2PjJ2Lm{nLks$>q6SM7q+I| zE5X+j`CGB4TJgJ{G8*W9Mb@k81fGs{PYu>}x7OlpQ|Q3peRf-gz?54%u6N3X{;icg z`)92PiRmh2?2*0-Po)h0E^h8)Us;W`-)nPUJXQ8tzgoRsEr>o=T;}Ukc`{*iyw3+- zs74{&j-Ko-MWW~Fy0LuM^6uS-CiMT%dtEU---E#aSP_Vv>s92@6{aW7 znVD1B0K&HzxaD+8l>1VQRpr@9u>*YfOD;(2NPXE!wCRq`3{(*uIQ(q7+ifyIDLrWI_Dl( zLr1;S@IH}e*Se_oV8sioTtPSAv5Oh7jfV0K;M1SJ zE||jRIg96ZM(8cO6Cdf!bWM6M6qkX~gTM)j={=Bhnov>q<`-3=XZ~{RT~^Who^~7x z+3EFc@Ew7IHqZ0L`Vo0`6)&z)kL53Mlh+lNrh#ud(ZA`Ob1Fd8O79C{fkSwvoAB>U zM9lG;WA{v#8eb^UE?%+U)bB`rw5My{gTzNoD#ZguRG+Tw`7Vf_ zxqI#NU+|%KbPrlHuzgJyT%oh++admcAe}f}_3ZZM$~#}vD@CH7J7R^C#-rwQIj Date: Thu, 21 Jul 2016 09:18:28 -0700 Subject: [PATCH 5/9] Fix Remove-CronTab to only remove the correct job(s) --- demos/crontab/CronTab/CronTab.psm1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/demos/crontab/CronTab/CronTab.psm1 b/demos/crontab/CronTab/CronTab.psm1 index b49201ce9..b694ddf52 100644 --- a/demos/crontab/CronTab/CronTab.psm1 +++ b/demos/crontab/CronTab/CronTab.psm1 @@ -11,6 +11,12 @@ class CronJob { [string] $Month [string] $DayOfWeek [string] $Command + + [string] ToString() + { + return "{0} {1} {2} {3} {4} {5}" -f + $this.Minute, $this.Hour, $this.DayOfMonth, $this.Month, $this.DayOfWeek, $this.Command + } } # Internal helper functions @@ -100,9 +106,9 @@ function Remove-CronJob { $newcrontab = [List[string]]::new() $found = $false + $JobAsString = $Job.ToString() foreach ($line in $crontab) { - $cronjob = ConvertTo-CronJob -crontab $line - if ((Compare-object $cronjob.psobject.properties $Job.psobject.properties) -eq $null) { + if ($JobAsString -ceq $line) { $found = $true } else { $newcrontab.Add($line) From d7d516a0662c578b15daec7e5d3ddc257cb41471 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 09:40:03 -0700 Subject: [PATCH 6/9] Don't use Invoke-Expression --- demos/crontab/CronTab/CronTab.psm1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/demos/crontab/CronTab/CronTab.psm1 b/demos/crontab/CronTab/CronTab.psm1 index b694ddf52..813de1834 100644 --- a/demos/crontab/CronTab/CronTab.psm1 +++ b/demos/crontab/CronTab/CronTab.psm1 @@ -47,14 +47,13 @@ function ConvertTo-CronJob ([String] $crontab) { $cronjob } -function Invoke-CronTab ([String] $user, [String] $arguments, [Switch] $noThrow) { +function Invoke-CronTab ([String] $user, [String[]] $arguments, [Switch] $noThrow) { If ($user -ne [String]::Empty) { - $arguments = "-u $UserName $arguments" + $arguments = Write-Output "-u" $UserName $arguments } - $cmd = "$crontabcmd $arguments 2>&1" - Write-Verbose $cmd - $output = Invoke-Expression $cmd + Write-Verbose "Running: $crontabcmd $arguments" + $output = & $crontabcmd @arguments 2>&1 if ($LastExitCode -ne 0 -and -not $noThrow) { $e = New-Object System.InvalidOperationException -ArgumentList $output.Exception.Message throw $e From 444769cb6b19a1963128815e38287260df9da77a Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 09:55:48 -0700 Subject: [PATCH 7/9] Add -Force to Remove-CronJob --- demos/crontab/CronTab/CronTab.psm1 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/demos/crontab/CronTab/CronTab.psm1 b/demos/crontab/CronTab/CronTab.psm1 index 813de1834..5dd80e846 100644 --- a/demos/crontab/CronTab/CronTab.psm1 +++ b/demos/crontab/CronTab/CronTab.psm1 @@ -85,6 +85,8 @@ function Remove-CronJob { Optional parameter to specify a specific user's cron table .PARAMETER Job Cron job object returned from Get-CronJob +.PARAMETER Force + Don't prompt when removing the cron job #> [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")] param ( @@ -97,7 +99,10 @@ function Remove-CronJob { [Alias("j")] [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [CronJob] - $Job + $Job, + + [Switch] + $Force ) process { @@ -118,7 +123,7 @@ function Remove-CronJob { $e = New-Object System.Exception -ArgumentList "Job not found" throw $e } - if ($pscmdlet.ShouldProcess($Job.Command,"Remove")) { + if ($Force -or $pscmdlet.ShouldProcess($Job.Command,"Remove")) { Import-CronTab -user $UserName -crontab $newcrontab } } From 3fb9315fced5c506e67a6099954a158112800654 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 09:56:14 -0700 Subject: [PATCH 8/9] cleanup demo script --- demos/crontab/crontab.ps1 | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/demos/crontab/crontab.ps1 b/demos/crontab/crontab.ps1 index 9e17acaed..91a396a66 100644 --- a/demos/crontab/crontab.ps1 +++ b/demos/crontab/crontab.ps1 @@ -1,21 +1,27 @@ Import-Module $PSScriptRoot/CronTab/CronTab.psd1 -# Get the existing cron jobs -Get-CronJob +Write-Host -Foreground Yellow "Remove all jobs to start, no prompting" +Get-CronJob | Remove-CronJob -Force -# New cron job to clean out tmp every day at 1am -New-CronJob -Command 'rm -rf /tmp/*' -Hour 1 +Write-Host -Foreground Yellow "Get the existing cron jobs" +Get-CronJob | Out-Host -# New cron job to start a build -New-CronJob -Command 'powershell -c "cd ~/src/PowerShell; ipmo ./build.psm1; Start-PSBuild"' -Hour 2 -DayOfWeek 1-5 +Write-Host -Foreground Yellow "New cron job to clean out tmp every day at 1am" +New-CronJob -Command 'rm -rf /tmp/*' -Hour 1 | Out-Host -# Sort them by data -Get-CronJob | Sort-Object Command +Write-Host -Foreground Yellow "Add some more jobs" +New-CronJob -Command 'python -c ~/scripts/backup_users' -Hour 2 -DayOfWeek 1-5 | Out-Host +New-CronJob -Command 'powershell -c "cd ~/src/PowerShell; ipmo ./build.psm1; Start-PSBuild"' -Hour 2 -DayOfWeek * | Out-Host -# Show in bash that the new cron job exists +Write-Host -Foreground Yellow "Show in bash that the new cron job exists" crontab -l -# Remove a cron job -Get-CronJob | Where-Object { $_.Command -match '^powershell.*' } | Remove-CronJob +Write-Host -Foreground Yellow "Get jobs that run every day" +Get-CronJob | Where-Object { $_.DayOfWeek -eq '*' -or $_.DayOfWeek -eq '1-7' } | Out-Host +Write-Host -Foreground Yellow "Remove one cron job, with prompting to confirm" +Get-CronJob | Where-Object { $_.Command -match '^powershell.*' } | Remove-CronJob | Out-Host + +Write-Host -Foreground Yellow "And the other job remains" +Get-CronJob | Out-Host From d300bf0ef7b6db191dae63b716e29278418b5c47 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 10:03:08 -0700 Subject: [PATCH 9/9] Add README --- demos/crontab/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 demos/crontab/README.md diff --git a/demos/crontab/README.md b/demos/crontab/README.md new file mode 100644 index 000000000..02138b1e4 --- /dev/null +++ b/demos/crontab/README.md @@ -0,0 +1,15 @@ +## CronTab demo + +This demo shows examining, creating, and removing cron jobs via crontab. + +Output of Get-CronJob is a strongly typed object with properties like DayOfWeek or Command. +Remove-CronJob prompts before removing the job unless you specifiy -Force. + +Tab completion of -UserName is supported, e.g. + +Get-CronJob -u + +NYI: no way to run crontab with sudo if necessary +NYI: ignoring shell variables or comments +NYI: New-CronJob -Description "..." (save in comments" +NYI: @reboot,@daily,@hourly,etc