PowerShell/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobStore.cs

684 lines
26 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Management.Automation;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
namespace Microsoft.PowerShell.ScheduledJob
{
/// <summary>
/// This class encapsulates the work of determining the file location where
/// a job definition will be stored and retrieved and where job runs will
/// be stored and retrieved. Scheduled job definitions are stored in a
/// location based on the current user. Job runs are stored in the
/// corresponding scheduled job definition location under an "Output"
/// directory, where each run will have a subdirectory with a name derived
/// from the job run date/time.
///
/// File Structure for "JobDefinitionFoo":
/// $env:User\AppData\Local\Windows\PowerShell\ScheduledJobs\JobDefinitionFoo\
/// ScheduledJobDefinition.xml
/// Output\
/// 110321-130942\
/// Status.xml
/// Results.xml
/// 110319-173502\
/// Status.xml
/// Results.xml
/// ...
/// </summary>
internal class ScheduledJobStore
{
#region Public Enums
public enum JobRunItem
{
None = 0,
Status = 1,
Results = 2
}
#endregion
#region Public Strings
public const string ScheduledJobsPath = @"Microsoft\Windows\PowerShell\ScheduledJobs";
public const string DefinitionFileName = "ScheduledJobDefinition";
public const string JobRunOutput = "Output";
public const string ScheduledJobDefExistsFQEID = "ScheduledJobDefExists";
#endregion
#region Public Methods
/// <summary>
/// Returns FileStream object for existing scheduled job definition.
/// Definition file is looked for in the default user local appdata path.
/// </summary>
/// <param name="definitionName">Scheduled job definition name.</param>
/// <param name="fileMode">File mode.</param>
/// <param name="fileAccess">File access.</param>
/// <param name="fileShare">File share.</param>
/// <returns>FileStream object.</returns>
public static FileStream GetFileForJobDefinition(
string definitionName,
FileMode fileMode,
FileAccess fileAccess,
FileShare fileShare)
{
if (string.IsNullOrEmpty(definitionName))
{
throw new PSArgumentException("definitionName");
}
string filePathName = GetFilePathName(definitionName, DefinitionFileName);
return File.Open(filePathName, fileMode, fileAccess, fileShare);
}
/// <summary>
/// Returns FileStream object for existing scheduled job definition.
/// Definition file is looked for in the path provided.
/// </summary>
/// <param name="definitionName">Scheduled job definition name.</param>
/// <param name="definitionPath">Scheduled job definition file path.</param>
/// <param name="fileMode">File mode.</param>
/// <param name="fileAccess">File share.</param>
/// <param name="fileShare">File share.</param>
/// <returns></returns>
public static FileStream GetFileForJobDefinition(
string definitionName,
string definitionPath,
FileMode fileMode,
FileAccess fileAccess,
FileShare fileShare)
{
if (string.IsNullOrEmpty(definitionName))
{
throw new PSArgumentException("definitionName");
}
if (string.IsNullOrEmpty(definitionPath))
{
throw new PSArgumentException("definitionPath");
}
string filePathName = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}\{2}.xml",
definitionPath, definitionName, DefinitionFileName);
return File.Open(filePathName, fileMode, fileAccess, fileShare);
}
/// <summary>
/// Checks the provided path against the the default path of scheduled jobs
/// for the current user.
/// </summary>
/// <param name="definitionPath">Path for scheduled job definitions.</param>
/// <returns>True if paths are equal.</returns>
public static bool IsDefaultUserPath(string definitionPath)
{
return definitionPath.Equals(GetJobDefinitionLocation(), StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Returns a FileStream object for a new scheduled job definition name.
/// </summary>
/// <param name="definitionName">Scheduled job definition name.</param>
/// <returns>FileStream object.</returns>
public static FileStream CreateFileForJobDefinition(
string definitionName)
{
if (string.IsNullOrEmpty(definitionName))
{
throw new PSArgumentException("definitionName");
}
string filePathName = CreateFilePathName(definitionName, DefinitionFileName);
return File.Create(filePathName);
}
/// <summary>
/// Returns an IEnumerable object of scheduled job definition names in
/// the job store.
/// </summary>
/// <returns>IEnumerable of job definition names.</returns>
public static IEnumerable<string> GetJobDefinitions()
{
// Directory names are identical to the corresponding scheduled job definition names.
string directoryPath = GetDirectoryPath();
IEnumerable<string> definitions = Directory.EnumerateDirectories(directoryPath);
return (definitions != null) ? definitions : new Collection<string>() as IEnumerable<string>;
}
/// <summary>
/// Returns a FileStream object for an existing scheduled job definition
/// run.
/// </summary>
/// <param name="definitionName">Scheduled job definition name.</param>
/// <param name="runStart">DateTime of job run start time.</param>
/// <param name="runItem">Job run item.</param>
/// <param name="fileAccess">File access.</param>
/// <param name="fileMode">File mode.</param>
/// <param name="fileShare">File share.</param>
/// <returns>FileStream object.</returns>
public static FileStream GetFileForJobRunItem(
string definitionName,
DateTime runStart,
JobRunItem runItem,
FileMode fileMode,
FileAccess fileAccess,
FileShare fileShare)
{
if (string.IsNullOrEmpty(definitionName))
{
throw new PSArgumentException("definitionName");
}
string filePathName = GetRunFilePathName(definitionName, runItem, runStart);
return File.Open(filePathName, fileMode, fileAccess, fileShare);
}
/// <summary>
/// Returns a FileStream object for a new scheduled job definition run.
/// </summary>
/// <param name="definitionOutputPath">Scheduled job definition path.</param>
/// <param name="runStart">DateTime of job run start time.</param>
/// <param name="runItem">Job run item.</param>
/// <returns>FileStream object.</returns>
public static FileStream CreateFileForJobRunItem(
string definitionOutputPath,
DateTime runStart,
JobRunItem runItem)
{
if (string.IsNullOrEmpty(definitionOutputPath))
{
throw new PSArgumentException("definitionOutputPath");
}
string filePathName = GetRunFilePathNameFromPath(definitionOutputPath, runItem, runStart);
// If the file already exists, we overwrite it because the job run
// can be updated multiple times while the job is running.
return File.Create(filePathName);
}
/// <summary>
/// Returns a collection of DateTime objects which specify job run directories
/// that are currently in the store.
/// </summary>
/// <param name="definitionName">Scheduled job definition name.</param>
/// <returns>Collection of DateTime objects.</returns>
public static Collection<DateTime> GetJobRunsForDefinition(
string definitionName)
{
if (string.IsNullOrEmpty(definitionName))
{
throw new PSArgumentException("definitionName");
}
string definitionOutputPath = GetJobRunOutputDirectory(definitionName);
return GetJobRunsForDefinitionPath(definitionOutputPath);
}
/// <summary>
/// Returns a collection of DateTime objects which specify job run directories
/// that are currently in the store.
/// </summary>
/// <param name="definitionOutputPath">Scheduled job definition job run Output path.</param>
/// <returns>Collection of DateTime objects.</returns>
public static Collection<DateTime> GetJobRunsForDefinitionPath(
string definitionOutputPath)
{
if (string.IsNullOrEmpty(definitionOutputPath))
{
throw new PSArgumentException("definitionOutputPath");
}
Collection<DateTime> jobRunInfos = new Collection<DateTime>();
IEnumerable<string> jobRuns = Directory.EnumerateDirectories(definitionOutputPath);
if (jobRuns != null)
{
// Job run directory names are the date/times that the job was started.
foreach (string jobRun in jobRuns)
{
DateTime jobRunDateTime;
int indx = jobRun.LastIndexOf('\\');
string jobRunName = (indx != -1) ? jobRun.Substring(indx + 1) : jobRun;
if (ConvertJobRunNameToDateTime(jobRunName, out jobRunDateTime))
{
jobRunInfos.Add(jobRunDateTime);
}
}
}
return jobRunInfos;
}
/// <summary>
/// Remove the job definition and all job runs from job store.
/// </summary>
/// <param name="definitionName">Scheduled Job Definition name.</param>
public static void RemoveJobDefinition(
string definitionName)
{
if (string.IsNullOrEmpty(definitionName))
{
throw new PSArgumentException("definitionName");
}
// Remove job runs, job definition file, and job definition directory.
string jobDefDirectory = GetJobDefinitionPath(definitionName);
Directory.Delete(jobDefDirectory, true);
}
/// <summary>
/// Renames the directory containing the old job definition name
/// to the new name provided.
/// </summary>
/// <param name="oldDefName">Existing job definition directory.</param>
/// <param name="newDefName">Renamed job definition directory.</param>
public static void RenameScheduledJobDefDir(
string oldDefName,
string newDefName)
{
if (string.IsNullOrEmpty(oldDefName))
{
throw new PSArgumentException("oldDefName");
}
if (string.IsNullOrEmpty(newDefName))
{
throw new PSArgumentException("newDefName");
}
string oldDirPath = GetJobDefinitionPath(oldDefName);
string newDirPath = GetJobDefinitionPath(newDefName);
Directory.Move(oldDirPath, newDirPath);
}
/// <summary>
/// Remove a single job definition job run from the job store.
/// </summary>
/// <param name="definitionName">Scheduled Job Definition name.</param>
/// <param name="runStart">DateTime of job run.</param>
public static void RemoveJobRun(
string definitionName,
DateTime runStart)
{
if (string.IsNullOrEmpty(definitionName))
{
throw new PSArgumentException("definitionName");
}
// Remove the job run files and directory.
string runDirectory = GetRunDirectory(definitionName, runStart);
Directory.Delete(runDirectory, true);
}
/// <summary>
/// Remove a single job definition job run from the job store.
/// </summary>
/// <param name="definitionOutputPath">Scheduled Job Definition Output path.</param>
/// <param name="runStart">DateTime of job run.</param>
public static void RemoveJobRunFromOutputPath(
string definitionOutputPath,
DateTime runStart)
{
if (string.IsNullOrEmpty(definitionOutputPath))
{
throw new PSArgumentException("definitionOutputPath");
}
// Remove the job run files and directory.
string runDirectory = GetRunDirectoryFromPath(definitionOutputPath, runStart);
Directory.Delete(runDirectory, true);
}
/// <summary>
/// Remove all job runs for this job definition.
/// </summary>
/// <param name="definitionName">Scheduled Job Definition name.</param>
public static void RemoveAllJobRuns(
string definitionName)
{
if (string.IsNullOrEmpty(definitionName))
{
throw new PSArgumentException("definitionName");
}
Collection<DateTime> jobRuns = GetJobRunsForDefinition(definitionName);
foreach (DateTime jobRun in jobRuns)
{
string jobRunPath = GetRunDirectory(definitionName, jobRun);
Directory.Delete(jobRunPath, true);
}
}
/// <summary>
/// Set read access on provided definition file for specified user.
/// </summary>
/// <param name="definitionName">Definition name.</param>
/// <param name="user">Account user name.</param>
public static void SetReadAccessOnDefinitionFile(
string definitionName,
string user)
{
string filePath = GetFilePathName(definitionName, DefinitionFileName);
// Get file security for existing file.
FileSecurity fileSecurity = new FileSecurity(
filePath,
AccessControlSections.Access);
// Create rule.
FileSystemAccessRule fileAccessRule = new FileSystemAccessRule(
user,
FileSystemRights.Read,
AccessControlType.Allow);
fileSecurity.AddAccessRule(fileAccessRule);
// Apply rule.
File.SetAccessControl(filePath, fileSecurity);
}
/// <summary>
/// Set write access on Output directory for provided definition for
/// specified user.
/// </summary>
/// <param name="definitionName">Definition name.</param>
/// <param name="user">Account user name.</param>
public static void SetWriteAccessOnJobRunOutput(
string definitionName,
string user)
{
string outputDirectoryPath = GetJobRunOutputDirectory(definitionName);
AddFullAccessToDirectory(user, outputDirectoryPath);
}
/// <summary>
/// Returns the directory path for job run output for the specified
/// scheduled job definition.
/// </summary>
/// <param name="definitionName">Definition name.</param>
/// <returns>Directory Path.</returns>
public static string GetJobRunOutputDirectory(
string definitionName)
{
if (string.IsNullOrEmpty(definitionName))
{
throw new PSArgumentException("definitionName");
}
return Path.Combine(GetJobDefinitionPath(definitionName), JobRunOutput);
}
/// <summary>
/// Gets the directory path for a Scheduled Job Definition.
/// </summary>
/// <returns>Directory Path.</returns>
public static string GetJobDefinitionLocation()
{
#if UNIX
return Path.Combine(Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE), "ScheduledJobs"));
#else
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ScheduledJobsPath);
#endif
}
public static void CreateDirectoryIfNotExists()
{
GetDirectoryPath();
}
#endregion
#region Private Methods
/// <summary>
/// Gets the directory path for Scheduled Jobs. Will create the directory if
/// it does not exist.
/// </summary>
/// <returns>Directory Path.</returns>
private static string GetDirectoryPath()
{
string pathName;
#if UNIX
pathName = Path.Combine(Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE), "ScheduledJobs"));
#else
pathName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ScheduledJobsPath);
#endif
if (!Directory.Exists(pathName))
{
Directory.CreateDirectory(pathName);
}
return pathName;
}
/// <summary>
/// Creates a ScheduledJob definition directory with provided definition name
/// along with a job run Output directory, and returns a file path/name.
/// ...\ScheduledJobs\definitionName\fileName.xml
/// ...\ScheduledJobs\definitionName\Output\
/// </summary>
/// <param name="definitionName">Definition name.</param>
/// <param name="fileName">File name.</param>
/// <returns>File path/name.</returns>
private static string CreateFilePathName(string definitionName, string fileName)
{
string filePath = GetJobDefinitionPath(definitionName);
string outputPath = GetJobRunOutputDirectory(definitionName);
if (Directory.Exists(filePath))
{
ScheduledJobException ex = new ScheduledJobException(StringUtil.Format(ScheduledJobErrorStrings.JobDefFileAlreadyExists, definitionName));
ex.FQEID = ScheduledJobDefExistsFQEID;
throw ex;
}
Directory.CreateDirectory(filePath);
Directory.CreateDirectory(outputPath);
return string.Format(CultureInfo.InstalledUICulture, @"{0}\{1}.xml", filePath, fileName);
}
/// <summary>
/// Returns a file path/name for an existing Scheduled job definition directory.
/// </summary>
/// <param name="definitionName">Definition name.</param>
/// <param name="fileName">File name.</param>
/// <returns>File path/name.</returns>
private static string GetFilePathName(string definitionName, string fileName)
{
string filePath = GetJobDefinitionPath(definitionName);
return string.Format(CultureInfo.InvariantCulture, @"{0}\{1}.xml", filePath, fileName);
}
/// <summary>
/// Gets the directory path for a Scheduled Job Definition.
/// </summary>
/// <param name="definitionName">Scheduled job definition name.</param>
/// <returns>Directory Path.</returns>
private static string GetJobDefinitionPath(string definitionName)
{
#if UNIX
return Path.Combine(Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE), "ScheduledJobs", definitionName);
#else
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
ScheduledJobsPath,
definitionName);
#endif
}
/// <summary>
/// Returns a directory path for an existing ScheduledJob run result directory.
/// </summary>
/// <param name="definitionName">Definition name.</param>
/// <param name="runStart">File name.</param>
/// <returns>Directory Path.</returns>
private static string GetRunDirectory(
string definitionName,
DateTime runStart)
{
string directoryPath = GetJobRunOutputDirectory(definitionName);
return string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", directoryPath,
ConvertDateTimeToJobRunName(runStart));
}
/// <summary>
/// Returns a directory path for an existing ScheduledJob run based on
/// provided definition Output directory path.
/// </summary>
/// <param name="definitionOutputPath">Output directory path.</param>
/// <param name="runStart">File name.</param>
/// <returns>Directory Path.</returns>
private static string GetRunDirectoryFromPath(
string definitionOutputPath,
DateTime runStart)
{
return string.Format(CultureInfo.InvariantCulture, @"{0}\{1}",
definitionOutputPath, ConvertDateTimeToJobRunName(runStart));
}
/// <summary>
/// Returns a file path/name for a run result file. Will create the
/// job run directory if it does not exist.
/// </summary>
/// <param name="definitionName">Definition name.</param>
/// <param name="runItem">Result type.</param>
/// <param name="runStart">Run date.</param>
/// <returns>File path/name.</returns>
private static string GetRunFilePathName(
string definitionName,
JobRunItem runItem,
DateTime runStart)
{
string directoryPath = GetJobRunOutputDirectory(definitionName);
string jobRunPath = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}",
directoryPath, ConvertDateTimeToJobRunName(runStart));
return string.Format(CultureInfo.InvariantCulture, @"{0}\{1}.xml", jobRunPath,
runItem.ToString());
}
/// <summary>
/// Returns a file path/name for a job run result, based on the passed in
/// job run output path. Will create the job run directory if it does not
/// exist.
/// </summary>
/// <param name="outputPath">Definition job run output path.</param>
/// <param name="runItem">Result type.</param>
/// <param name="runStart">Run date.</param>
/// <returns></returns>
private static string GetRunFilePathNameFromPath(
string outputPath,
JobRunItem runItem,
DateTime runStart)
{
string jobRunPath = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}",
outputPath, ConvertDateTimeToJobRunName(runStart));
if (!Directory.Exists(jobRunPath))
{
// Create directory for this job run date.
Directory.CreateDirectory(jobRunPath);
}
return string.Format(CultureInfo.InvariantCulture, @"{0}\{1}.xml", jobRunPath,
runItem.ToString());
}
private static void AddFullAccessToDirectory(
string user,
string directoryPath)
{
// Create rule.
DirectoryInfo info = new DirectoryInfo(directoryPath);
DirectorySecurity dSecurity = info.GetAccessControl();
FileSystemAccessRule fileAccessRule = new FileSystemAccessRule(
user,
FileSystemRights.FullControl,
InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
PropagationFlags.None,
AccessControlType.Allow);
// Apply rule.
dSecurity.AddAccessRule(fileAccessRule);
info.SetAccessControl(dSecurity);
}
//
// String format: 'YYYYMMDD-HHMMSS-SSS'
// ,where SSS is milliseconds.
//
private static string ConvertDateTimeToJobRunName(DateTime dt)
{
return string.Format(CultureInfo.InvariantCulture,
@"{0:d4}{1:d2}{2:d2}-{3:d2}{4:d2}{5:d2}-{6:d3}",
dt.Year, dt.Month, dt.Day,
dt.Hour, dt.Minute, dt.Second, dt.Millisecond);
}
/// <summary>
/// Converts a jobRun name string to an equivalent DateTime.
/// </summary>
/// <param name="jobRunName"></param>
/// <param name="jobRun"></param>
/// <returns></returns>
internal static bool ConvertJobRunNameToDateTime(string jobRunName, out DateTime jobRun)
{
if (jobRunName == null || jobRunName.Length != 19)
{
jobRun = new DateTime();
return false;
}
int year = 0;
int month = 0;
int day = 0;
int hour = 0;
int minute = 0;
int second = 0;
int msecs = 0;
bool success = true;
try
{
year = Convert.ToInt32(jobRunName.Substring(0, 4));
month = Convert.ToInt32(jobRunName.Substring(4, 2));
day = Convert.ToInt32(jobRunName.Substring(6, 2));
hour = Convert.ToInt32(jobRunName.Substring(9, 2));
minute = Convert.ToInt32(jobRunName.Substring(11, 2));
second = Convert.ToInt32(jobRunName.Substring(13, 2));
msecs = Convert.ToInt32(jobRunName.Substring(16, 3));
}
catch (FormatException)
{
success = false;
}
catch (OverflowException)
{
success = false;
}
if (success)
{
jobRun = new DateTime(year, month, day, hour, minute, second, msecs);
}
else
{
jobRun = new DateTime();
}
return success;
}
#endregion
}
}