diff --git a/demos/crontab.ps1 b/demos/crontab.ps1 deleted file mode 100644 index d90e2dbdb..000000000 --- a/demos/crontab.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -# Use the crontab module available at ./modules/CronTab - -# Get the existing cron jobs - -# Sort them by data - -# Create a new one using a DateTime object - -# Show in bash that the new cron job exists 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 68% rename from demos/modules/CronTab/CronTab.psm1 rename to demos/crontab/CronTab/CronTab.psm1 index f8bd2e7c4..5dd80e846 100644 --- a/demos/modules/CronTab/CronTab.psm1 +++ b/demos/crontab/CronTab/CronTab.psm1 @@ -1,22 +1,31 @@ + +using namespace System.Collections.Generic +using namespace System.Management.Automation + $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 + + [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 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 = $null + $crontab = @() } else { throw $crontab.Exception @@ -27,8 +36,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]; @@ -39,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 @@ -78,24 +85,37 @@ 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 ( - [Alias("u")][Parameter(Mandatory=$false)][String] $UserName, - [Alias("j")][Parameter(Mandatory=$true,ValueFromPipeline=$true)][PSTypeName("CronJob")] $Job #TODO use CronJob type + [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, + + [Switch] + $Force ) process { [string[]] $crontab = Get-CronTab -user $UserName - [string[]] $newcrontab = $null + $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 += $line + $newcrontab.Add($line) } } @@ -103,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 } } @@ -140,7 +160,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 = "*", @@ -173,7 +198,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 ) @@ -187,3 +212,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 + } + } +} 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 diff --git a/demos/crontab/crontab.ps1 b/demos/crontab/crontab.ps1 new file mode 100644 index 000000000..91a396a66 --- /dev/null +++ b/demos/crontab/crontab.ps1 @@ -0,0 +1,27 @@ + +Import-Module $PSScriptRoot/CronTab/CronTab.psd1 + +Write-Host -Foreground Yellow "Remove all jobs to start, no prompting" +Get-CronJob | Remove-CronJob -Force + +Write-Host -Foreground Yellow "Get the existing cron jobs" +Get-CronJob | Out-Host + +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 + +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 + +Write-Host -Foreground Yellow "Show in bash that the new cron job exists" +crontab -l + +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 diff --git a/demos/modules/CronTab/CronTab.psd1 b/demos/modules/CronTab/CronTab.psd1 deleted file mode 100644 index 23d9d95f0..000000000 Binary files a/demos/modules/CronTab/CronTab.psd1 and /dev/null differ