mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Add missing files for OPS [SD:717473]
This commit is contained in:
committed by
Dongbo Wang
parent
b4089414de
commit
49fea9dac9
@@ -0,0 +1,862 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Management.Automation;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// A cmdlet to retrieve time zone information.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "TimeZone", DefaultParameterSetName = "Name",
|
||||
HelpUri = "http://go.microsoft.com/fwlink/?LinkId=799468")]
|
||||
[Alias("gtz")]
|
||||
public class GetTimeZoneCommand : PSCmdlet
|
||||
{
|
||||
#region Parameters
|
||||
|
||||
/// <summary>
|
||||
/// A list of the local time zone ids that the cmdlet should look up.
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Id")]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
||||
public string[] Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that the cmdlet should produce a collection of the
|
||||
/// TimeZoneInfo objects that are available on the system.
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ParameterSetName = "ListAvailable")]
|
||||
public SwitchParameter ListAvailable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of the local time zone names that the cmdlet should look up.
|
||||
/// </summary>
|
||||
[Parameter(Position = 0, ValueFromPipeline = true, ParameterSetName = "Name")]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
||||
public string[] Name { get; set; }
|
||||
|
||||
#endregion Parameters
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of the ProcessRecord method for Get-TimeZone
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
// make sure we've got the latest time zone settings
|
||||
TimeZoneHelper.ClearCachedData();
|
||||
|
||||
if (this.ParameterSetName.Equals("ListAvailable", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// output the list of all available time zones
|
||||
WriteObject(TimeZoneInfo.GetSystemTimeZones(), true);
|
||||
}
|
||||
else if (this.ParameterSetName.Equals("Id", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// lookup each time zone id
|
||||
foreach (string tzid in Id)
|
||||
{
|
||||
try
|
||||
{
|
||||
WriteObject(TimeZoneInfo.FindSystemTimeZoneById(tzid));
|
||||
}
|
||||
#if CORECLR
|
||||
// TimeZoneNotFoundException is thrown by TimeZoneInfo, but not
|
||||
// publicly visible (so can't be caught), so for now we're catching
|
||||
// the parent exception time. This should be removed once the more
|
||||
// specific exception is available.
|
||||
catch (Exception e)
|
||||
#else
|
||||
catch (TimeZoneNotFoundException e)
|
||||
#endif
|
||||
{
|
||||
WriteError(new ErrorRecord(e, TimeZoneHelper.TimeZoneNotFoundError,
|
||||
ErrorCategory.InvalidArgument, "Id"));
|
||||
}
|
||||
}
|
||||
}
|
||||
else // ParameterSetName == "Name"
|
||||
{
|
||||
if (null != Name)
|
||||
{
|
||||
// lookup each time zone name (or wildcard pattern)
|
||||
foreach (string tzname in Name)
|
||||
{
|
||||
TimeZoneInfo[] timeZones = TimeZoneHelper.LookupSystemTimeZoneInfoByName(tzname);
|
||||
if (0 < timeZones.Length)
|
||||
{
|
||||
// manually process each object in the array, so if there is only a single
|
||||
// entry then the returned type is TimeZoneInfo and not TimeZoneInfo[], and
|
||||
// it can be pipelined to Set-TimeZone more easily
|
||||
foreach (TimeZoneInfo timeZone in timeZones)
|
||||
{
|
||||
WriteObject(timeZone);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string message = string.Format(CultureInfo.InvariantCulture,
|
||||
TimeZoneResources.TimeZoneNameNotFound, tzname);
|
||||
#if CORECLR
|
||||
// Because .NET Core does not currently expose the TimeZoneNotFoundException
|
||||
// we need to throw the more generic parent exception class for the time being.
|
||||
// This should be removed once the correct exception class is available.
|
||||
Exception e = new Exception(message);
|
||||
#else
|
||||
Exception e = new TimeZoneNotFoundException(message);
|
||||
#endif
|
||||
WriteError(new ErrorRecord(e, TimeZoneHelper.TimeZoneNotFoundError,
|
||||
ErrorCategory.InvalidArgument, "Name"));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// return the current system local time zone
|
||||
WriteObject(TimeZoneInfo.Local);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A cmdlet to set the system's local time zone.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Set, "TimeZone",
|
||||
SupportsShouldProcess = true,
|
||||
DefaultParameterSetName = "Name",
|
||||
HelpUri = "http://go.microsoft.com/fwlink/?LinkId=799469")]
|
||||
[Alias("stz")]
|
||||
public class SetTimeZoneCommand : PSCmdlet
|
||||
{
|
||||
#region string constants
|
||||
|
||||
private const string TimeZoneTarget = "Local System";
|
||||
|
||||
#endregion string constants
|
||||
|
||||
#region Parameters
|
||||
|
||||
/// <summary>
|
||||
/// The name of the local time zone that the system should use.
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ParameterSetName = "Id", ValueFromPipelineByPropertyName = true)]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A TimeZoneInfo object identifying the local time zone that the system should use.
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "InputObject", ValueFromPipeline = true)]
|
||||
public TimeZoneInfo InputObject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the local time zone that the system should use.
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "Name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Request return of the new local time zone as a TimeZoneInfo object
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public SwitchParameter PassThru { get; set; }
|
||||
|
||||
#endregion Parameters
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of the ProcessRecord method for Get-TimeZone
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Since Name is not a parameter of this method, it confuses FXCop. It is the appropriate value for the exception.")]
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
// make sure we've got fresh data, in case the requested time zone was added
|
||||
// to the system (registry) after our process was started
|
||||
TimeZoneHelper.ClearCachedData();
|
||||
|
||||
// acquire a TimeZoneInfo if one wasn't supplied.
|
||||
if (this.ParameterSetName.Equals("Id", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
InputObject = TimeZoneInfo.FindSystemTimeZoneById(Id);
|
||||
}
|
||||
#if CORECLR
|
||||
// TimeZoneNotFoundException is thrown by TimeZoneInfo, but not
|
||||
// publicly visible (so can't be caught), so for now we're catching
|
||||
// the parent exception time. This should be removed once the more
|
||||
// specific exception is available.
|
||||
catch (Exception e)
|
||||
#else
|
||||
catch (TimeZoneNotFoundException e)
|
||||
#endif
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
e,
|
||||
TimeZoneHelper.TimeZoneNotFoundError,
|
||||
ErrorCategory.InvalidArgument,
|
||||
"Id"));
|
||||
}
|
||||
}
|
||||
else if (this.ParameterSetName.Equals("Name", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// lookup the time zone name and make sure we have one (and only one) match
|
||||
TimeZoneInfo[] timeZones = TimeZoneHelper.LookupSystemTimeZoneInfoByName(Name);
|
||||
if (0 == timeZones.Length)
|
||||
{
|
||||
string message = string.Format(CultureInfo.InvariantCulture,
|
||||
TimeZoneResources.TimeZoneNameNotFound, Name);
|
||||
#if CORECLR
|
||||
// Because .NET Core does not currently expose the TimeZoneNotFoundException
|
||||
// we need to throw the more generic parent exception class for the time being.
|
||||
// This should be removed once the correct exception class is available.
|
||||
Exception e = new Exception(message);
|
||||
#else
|
||||
Exception e = new TimeZoneNotFoundException(message);
|
||||
#endif
|
||||
ThrowTerminatingError(new ErrorRecord(e,
|
||||
TimeZoneHelper.TimeZoneNotFoundError,
|
||||
ErrorCategory.InvalidArgument,
|
||||
"Name"));
|
||||
}
|
||||
else if (1 < timeZones.Length)
|
||||
{
|
||||
string message = string.Format(CultureInfo.InvariantCulture,
|
||||
TimeZoneResources.MultipleMatchingTimeZones, Name);
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new PSArgumentException(message, "Name"),
|
||||
TimeZoneHelper.MultipleMatchingTimeZonesError,
|
||||
ErrorCategory.InvalidArgument,
|
||||
"Name"));
|
||||
}
|
||||
else
|
||||
{
|
||||
InputObject = timeZones[0];
|
||||
}
|
||||
}
|
||||
else // ParameterSetName == "InputObject"
|
||||
{
|
||||
try
|
||||
{
|
||||
// a TimeZoneInfo object was supplied, so use it to make sure we can find
|
||||
// a backing system time zone, otherwise it's an error condition
|
||||
InputObject = TimeZoneInfo.FindSystemTimeZoneById(InputObject.Id);
|
||||
}
|
||||
#if CORECLR
|
||||
// TimeZoneNotFoundException is thrown by TimeZoneInfo, but not
|
||||
// publicly visible (so can't be caught), so for now we're catching
|
||||
// the parent exception time. This should be removed once the more
|
||||
// specific exception is available.
|
||||
catch (Exception e)
|
||||
#else
|
||||
catch (TimeZoneNotFoundException e)
|
||||
#endif
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
e,
|
||||
TimeZoneHelper.TimeZoneNotFoundError,
|
||||
ErrorCategory.InvalidArgument,
|
||||
"InputObject"));
|
||||
}
|
||||
}
|
||||
|
||||
if (ShouldProcess(TimeZoneTarget))
|
||||
{
|
||||
bool acquireAccess = false;
|
||||
try
|
||||
{
|
||||
// check to see if permission to set the time zone is already enabled for this process
|
||||
if (!HasAccess)
|
||||
{
|
||||
// acquire permissions to set the timezone
|
||||
SetAccessToken(true);
|
||||
acquireAccess = true;
|
||||
}
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(e,
|
||||
TimeZoneHelper.InsufficientPermissionsError,
|
||||
ErrorCategory.PermissionDenied, null));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// construct and populate a new DYNAMIC_TIME_ZONE_INFORMATION structure
|
||||
NativeMethods.DYNAMIC_TIME_ZONE_INFORMATION dtzi = new NativeMethods.DYNAMIC_TIME_ZONE_INFORMATION();
|
||||
dtzi.Bias -= (int)InputObject.BaseUtcOffset.TotalMinutes;
|
||||
dtzi.StandardName = InputObject.StandardName;
|
||||
dtzi.DaylightName = InputObject.DaylightName;
|
||||
dtzi.TimeZoneKeyName = InputObject.Id;
|
||||
|
||||
// Request time zone transition information for the current year
|
||||
NativeMethods.TIME_ZONE_INFORMATION tzi = new NativeMethods.TIME_ZONE_INFORMATION();
|
||||
if (!NativeMethods.GetTimeZoneInformationForYear((ushort)DateTime.Now.Year, ref dtzi, ref tzi))
|
||||
{
|
||||
ThrowWin32Error();
|
||||
}
|
||||
|
||||
// copy over the transition times
|
||||
dtzi.StandardBias = tzi.StandardBias;
|
||||
dtzi.StandardDate = tzi.StandardDate;
|
||||
dtzi.DaylightBias = tzi.DaylightBias;
|
||||
dtzi.DaylightDate = tzi.DaylightDate;
|
||||
|
||||
// set the new local time zone for the system
|
||||
if (!NativeMethods.SetDynamicTimeZoneInformation(ref dtzi))
|
||||
{
|
||||
ThrowWin32Error();
|
||||
}
|
||||
|
||||
#if !CORECLR
|
||||
// broadcast a WM_SETTINGCHANGE notification message to all top-level windows so that they
|
||||
// know to update their notion of the current system time (and time zone) if applicable
|
||||
int result = 0;
|
||||
NativeMethods.SendMessageTimeout((IntPtr)NativeMethods.HWND_BROADCAST, NativeMethods.WM_SETTINGCHANGE,
|
||||
(IntPtr)0, "intl", NativeMethods.SMTO_ABORTIFHUNG, 5000, ref result);
|
||||
#endif
|
||||
|
||||
// clear the time zone data or this PowerShell session
|
||||
// will not recognize the new time zone settings
|
||||
TimeZoneHelper.ClearCachedData();
|
||||
|
||||
if (PassThru.IsPresent)
|
||||
{
|
||||
// return the TimeZoneInfo object for the (new) current local time zone
|
||||
WriteObject(TimeZoneInfo.Local);
|
||||
}
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(e,
|
||||
TimeZoneHelper.SetTimeZoneFailedError,
|
||||
ErrorCategory.FromStdErr, null));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (acquireAccess)
|
||||
{
|
||||
// reset the permissions
|
||||
SetAccessToken(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PassThru.IsPresent)
|
||||
{
|
||||
// show the user the time zone settings that would have been used.
|
||||
WriteObject(InputObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Helper functions
|
||||
|
||||
/// <summary>
|
||||
/// True if the current process has access to change the time zone setting.
|
||||
/// </summary>
|
||||
protected bool HasAccess
|
||||
{
|
||||
get
|
||||
{
|
||||
bool hasAccess = false;
|
||||
|
||||
// open the access token for the current process
|
||||
IntPtr hToken = IntPtr.Zero;
|
||||
IntPtr hProcess = NativeMethods.GetCurrentProcess();
|
||||
if (!NativeMethods.OpenProcessToken(hProcess,
|
||||
NativeMethods.TOKEN_ADJUST_PRIVILEGES | NativeMethods.TOKEN_QUERY, ref hToken))
|
||||
{
|
||||
ThrowWin32Error();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// setup the privileges being checked
|
||||
NativeMethods.PRIVILEGE_SET ps = new NativeMethods.PRIVILEGE_SET()
|
||||
{
|
||||
PrivilegeCount = 1,
|
||||
Control = 1,
|
||||
Luid = 0,
|
||||
Attributes = NativeMethods.SE_PRIVILEGE_ENABLED,
|
||||
};
|
||||
|
||||
// lookup the Luid of the SeTimeZonePrivilege
|
||||
if (!NativeMethods.LookupPrivilegeValue(null, NativeMethods.SE_TIME_ZONE_NAME, ref ps.Luid))
|
||||
{
|
||||
ThrowWin32Error();
|
||||
}
|
||||
|
||||
// set the privilege for the open access token
|
||||
if (!NativeMethods.PrivilegeCheck(hToken, ref ps, ref hasAccess))
|
||||
{
|
||||
ThrowWin32Error();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.CloseHandle(hToken);
|
||||
}
|
||||
|
||||
return (hasAccess);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the SeTimeZonePrivilege, which controls access to the SetDynamicTimeZoneInformation API
|
||||
/// </summary>
|
||||
/// <param name="enable">Set to true to enable (or false to disable) the privilege.</param>
|
||||
protected void SetAccessToken(bool enable)
|
||||
{
|
||||
// open the access token for the current process
|
||||
IntPtr hToken = IntPtr.Zero;
|
||||
IntPtr hProcess = NativeMethods.GetCurrentProcess();
|
||||
if (!NativeMethods.OpenProcessToken(hProcess,
|
||||
NativeMethods.TOKEN_ADJUST_PRIVILEGES | NativeMethods.TOKEN_QUERY, ref hToken))
|
||||
{
|
||||
ThrowWin32Error();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// setup the privileges being requested
|
||||
NativeMethods.TOKEN_PRIVILEGES tp = new NativeMethods.TOKEN_PRIVILEGES()
|
||||
{
|
||||
PrivilegeCount = 1,
|
||||
Luid = 0,
|
||||
Attributes = (enable ? NativeMethods.SE_PRIVILEGE_ENABLED : 0),
|
||||
};
|
||||
|
||||
// lookup the Luid of the SeTimeZonePrivilege
|
||||
if (!NativeMethods.LookupPrivilegeValue(null, NativeMethods.SE_TIME_ZONE_NAME, ref tp.Luid))
|
||||
{
|
||||
ThrowWin32Error();
|
||||
}
|
||||
|
||||
// set the privilege for the open access token
|
||||
if (!NativeMethods.AdjustTokenPrivileges(hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero))
|
||||
{
|
||||
ThrowWin32Error();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.CloseHandle(hToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Win32 error code from GetLastError and throw an exception.
|
||||
/// </summary>
|
||||
protected void ThrowWin32Error()
|
||||
{
|
||||
int error = Marshal.GetLastWin32Error();
|
||||
throw new Win32Exception(error);
|
||||
}
|
||||
|
||||
#endregion Helper functions
|
||||
|
||||
#region Win32 interop helper
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Private constructor to prevent instantiation
|
||||
/// </summary>
|
||||
private NativeMethods()
|
||||
{
|
||||
}
|
||||
|
||||
#region Native DLL locations
|
||||
|
||||
#if CORECLR
|
||||
private const string SetDynamicTimeZoneApiDllName = "api-ms-win-core-timezone-l1-1-0.dll";
|
||||
private const string GetTimeZoneInformationForYearApiDllName = "api-ms-win-core-timezone-l1-1-0.dll";
|
||||
private const string GetCurrentProcessApiDllName = "api-ms-win-downlevel-kernel32-l1-1-0.dll";
|
||||
private const string OpenProcessTokenApiDllName = "api-ms-win-downlevel-advapi32-l1-1-1.dll";
|
||||
private const string LookupPrivilegeTokenApiDllName = "api-ms-win-downlevel-advapi32-l4-1-0.dll";
|
||||
private const string PrivilegeCheckApiDllName = "api-ms-win-downlevel-advapi32-l1-1-1.dll";
|
||||
private const string AdjustTokenPrivilegesApiDllName = "api-ms-win-downlevel-advapi32-l1-1-1.dll";
|
||||
private const string CloseHandleApiDllName = "api-ms-win-downlevel-kernel32-l1-1-0.dll";
|
||||
private const string SendMessageTimeoutApiDllName = "ext-ms-win-rtcore-ntuser-window-ext-l1-1-0.dll";
|
||||
#else
|
||||
private const string SetDynamicTimeZoneApiDllName = "kernel32.dll";
|
||||
private const string GetTimeZoneInformationForYearApiDllName = "kernel32.dll";
|
||||
private const string GetCurrentProcessApiDllName = "kernel32.dll";
|
||||
private const string OpenProcessTokenApiDllName = "advapi32.dll";
|
||||
private const string LookupPrivilegeTokenApiDllName = "advapi32.dll";
|
||||
private const string PrivilegeCheckApiDllName = "advapi32.dll";
|
||||
private const string AdjustTokenPrivilegesApiDllName = "advapi32.dll";
|
||||
private const string CloseHandleApiDllName = "kernel32.dll";
|
||||
private const string SendMessageTimeoutApiDllName = "user32.dll";
|
||||
#endif
|
||||
|
||||
#endregion Native DLL locations
|
||||
|
||||
#region Win32 SetDynamicTimeZoneInformation imports
|
||||
|
||||
/// <summary>
|
||||
/// Used to marshal win32 SystemTime structure to managed code layer
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SystemTime
|
||||
{
|
||||
/// <summary>
|
||||
/// The year.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Year;
|
||||
/// <summary>
|
||||
/// The month.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Month;
|
||||
/// <summary>
|
||||
/// The day of the week.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short DayOfWeek;
|
||||
/// <summary>
|
||||
/// The day of the month.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Day;
|
||||
/// <summary>
|
||||
/// The hour.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Hour;
|
||||
/// <summary>
|
||||
/// The minute.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Minute;
|
||||
/// <summary>
|
||||
/// The second.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Second;
|
||||
/// <summary>
|
||||
/// The millisecond.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Milliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to marshal win32 DYNAMIC_TIME_ZONE_INFORMATION structure to managed code layer
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct DYNAMIC_TIME_ZONE_INFORMATION
|
||||
{
|
||||
/// <summary>
|
||||
/// The current bias for local time translation on this computer, in minutes.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I4)]
|
||||
public int Bias;
|
||||
/// <summary>
|
||||
/// A description for standard time.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
|
||||
public string StandardName;
|
||||
/// <summary>
|
||||
/// A SystemTime structure that contains a date and local time when the transition from daylight saving time to standard time occurs on this operating system.
|
||||
/// </summary>
|
||||
public SystemTime StandardDate;
|
||||
/// <summary>
|
||||
/// The bias value to be used during local time translations that occur during standard time.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I4)]
|
||||
public int StandardBias;
|
||||
/// <summary>
|
||||
/// A description for daylight saving time (DST).
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
|
||||
public string DaylightName;
|
||||
/// <summary>
|
||||
/// A SystemTime structure that contains a date and local time when the transition from standard time to daylight saving time occurs on this operating system.
|
||||
/// </summary>
|
||||
public SystemTime DaylightDate;
|
||||
/// <summary>
|
||||
/// The bias value to be used during local time translations that occur during daylight saving time.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I4)]
|
||||
public int DaylightBias;
|
||||
/// <summary>
|
||||
/// The name of the time zone registry key on the local computer.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x80)]
|
||||
public string TimeZoneKeyName;
|
||||
/// <summary>
|
||||
/// Indicates whether dynamic daylight saving time is disabled.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U1)]
|
||||
public bool DynamicDaylightTimeDisabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to marshal win32 TIME_ZONE_INFORMATION structure to managed code layer
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct TIME_ZONE_INFORMATION
|
||||
{
|
||||
/// <summary>
|
||||
/// The current bias for local time translation on this computer, in minutes.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I4)]
|
||||
public int Bias;
|
||||
/// <summary>
|
||||
/// A description for standard time.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
|
||||
public string StandardName;
|
||||
/// <summary>
|
||||
/// A SystemTime structure that contains a date and local time when the transition from daylight saving time to standard time occurs on this operating system.
|
||||
/// </summary>
|
||||
public SystemTime StandardDate;
|
||||
/// <summary>
|
||||
/// The bias value to be used during local time translations that occur during standard time.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I4)]
|
||||
public int StandardBias;
|
||||
/// <summary>
|
||||
/// A description for daylight saving time (DST).
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
|
||||
public string DaylightName;
|
||||
/// <summary>
|
||||
/// A SystemTime structure that contains a date and local time when the transition from standard time to daylight saving time occurs on this operating system.
|
||||
/// </summary>
|
||||
public SystemTime DaylightDate;
|
||||
/// <summary>
|
||||
/// The bias value to be used during local time translations that occur during daylight saving time.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I4)]
|
||||
public int DaylightBias;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PInvoke SetDynamicTimeZoneInformation API
|
||||
/// </summary>
|
||||
/// <param name="lpTimeZoneInformation">A DYNAMIC_TIME_ZONE_INFORMATION structure representing the desired local time zone.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport(SetDynamicTimeZoneApiDllName, SetLastError = true)]
|
||||
public static extern bool SetDynamicTimeZoneInformation([In] ref DYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation);
|
||||
|
||||
[DllImport(GetTimeZoneInformationForYearApiDllName, SetLastError = true)]
|
||||
public static extern bool GetTimeZoneInformationForYear([In] ushort wYear, [In] ref DYNAMIC_TIME_ZONE_INFORMATION pdtzi, ref TIME_ZONE_INFORMATION ptzi);
|
||||
|
||||
#endregion Win32 SetDynamicTimeZoneInformation imports
|
||||
|
||||
#region Win32 AdjustTokenPrivilege imports
|
||||
|
||||
/// <summary>
|
||||
/// Definition of TOKEN_QUERY constant from Win32 API
|
||||
/// </summary>
|
||||
public const int TOKEN_QUERY = 0x00000008;
|
||||
|
||||
/// <summary>
|
||||
/// Definition of TOKEN_ADJUST_PRIVILEGES constant from Win32 API
|
||||
/// </summary>
|
||||
public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
|
||||
|
||||
/// <summary>
|
||||
/// Definition of SE_PRIVILEGE_ENABLED constant from Win32 API
|
||||
/// </summary>
|
||||
public const int SE_PRIVILEGE_ENABLED = 0x00000002;
|
||||
|
||||
/// <summary>
|
||||
/// Definition of SE_TIME_ZONE_NAME constant from Win32 API
|
||||
/// </summary>
|
||||
public const string SE_TIME_ZONE_NAME = "SeTimeZonePrivilege"; //http://msdn.microsoft.com/en-us/library/bb530716(VS.85).aspx
|
||||
|
||||
/// <summary>
|
||||
/// PInvoke GetCurrentProcess API
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DllImport(GetCurrentProcessApiDllName, ExactSpelling = true)]
|
||||
public static extern IntPtr GetCurrentProcess();
|
||||
|
||||
/// <summary>
|
||||
/// PInvoke OpenProcessToken API
|
||||
/// </summary>
|
||||
/// <param name="ProcessHandle"></param>
|
||||
/// <param name="DesiredAccess"></param>
|
||||
/// <param name="TokenHandle"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport(OpenProcessTokenApiDllName, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle);
|
||||
|
||||
/// <summary>
|
||||
/// PInvoke LookupPrivilegeValue API
|
||||
/// </summary>
|
||||
/// <param name="lpSystemName"></param>
|
||||
/// <param name="lpName"></param>
|
||||
/// <param name="lpLuid"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport(LookupPrivilegeTokenApiDllName, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref long lpLuid);
|
||||
|
||||
/// <summary>
|
||||
/// PInvoke PrivilegeCheck API
|
||||
/// </summary>
|
||||
/// <param name="ClientToken"></param>
|
||||
/// <param name="RequiredPrivileges"></param>
|
||||
/// <param name="pfResult"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport(PrivilegeCheckApiDllName, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool PrivilegeCheck(IntPtr ClientToken, ref PRIVILEGE_SET RequiredPrivileges, ref bool pfResult);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// PInvoke AdjustTokenPrivilege API
|
||||
/// </summary>
|
||||
/// <param name="TokenHandle"></param>
|
||||
/// <param name="DisableAllPrivilegs"></param>
|
||||
/// <param name="NewState"></param>
|
||||
/// <param name="BufferLength"></param>
|
||||
/// <param name="PreviousState"></param>
|
||||
/// <param name="ReturnLength"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport(AdjustTokenPrivilegesApiDllName, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivilegs,
|
||||
ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, IntPtr ReturnLength);
|
||||
|
||||
/// <summary>
|
||||
/// PInvoke CloseHandle API
|
||||
/// </summary>
|
||||
/// <param name="hObject"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport(CloseHandleApiDllName, ExactSpelling = true, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
/// <summary>
|
||||
/// Used to marshal win32 PRIVILEGE_SET structure to managed code layer
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct PRIVILEGE_SET
|
||||
{
|
||||
public int PrivilegeCount;
|
||||
public int Control;
|
||||
public long Luid;
|
||||
public int Attributes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to marshal win32 TOKEN_PRIVILEGES structure to managed code layer
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct TOKEN_PRIVILEGES
|
||||
{
|
||||
public int PrivilegeCount;
|
||||
public long Luid;
|
||||
public int Attributes;
|
||||
}
|
||||
|
||||
#endregion Win32 AdjustTokenPrivilege imports
|
||||
|
||||
#region Win32 SendMessage imports
|
||||
|
||||
/// <summary>
|
||||
/// Definition of WM_SETTINGCHANGE constant from Win32 API
|
||||
/// </summary>
|
||||
public const int WM_SETTINGCHANGE = 0x001A;
|
||||
|
||||
/// <summary>
|
||||
/// Definition of HWND_BROADCAST constant from Win32 API
|
||||
/// </summary>
|
||||
public const int HWND_BROADCAST = (-1);
|
||||
|
||||
/// <summary>
|
||||
/// Definition of SMTO_ABORTIFHUNG constant from Win32 API
|
||||
/// </summary>
|
||||
public const int SMTO_ABORTIFHUNG = 0x0002;
|
||||
|
||||
/// <summary>
|
||||
/// PInvoke SendMessageTimeout API
|
||||
/// </summary>
|
||||
/// <param name="hWnd"></param>
|
||||
/// <param name="Msg"></param>
|
||||
/// <param name="wParam"></param>
|
||||
/// <param name="lParam"></param>
|
||||
/// <param name="fuFlags"></param>
|
||||
/// <param name="uTimeout"></param>
|
||||
/// <param name="lpdwResult"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport(SendMessageTimeoutApiDllName, SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, int fuFlags, int uTimeout, ref int lpdwResult);
|
||||
|
||||
#endregion Win32 SendMessage imports
|
||||
}
|
||||
|
||||
#endregion Win32 interop helper
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// static Helper class for working with system time zones.
|
||||
/// </summary>
|
||||
internal static class TimeZoneHelper
|
||||
{
|
||||
#region Error Ids
|
||||
|
||||
internal const string TimeZoneNotFoundError = "TimeZoneNotFound";
|
||||
internal const string MultipleMatchingTimeZonesError = "MultipleMatchingTimeZones";
|
||||
internal const string InsufficientPermissionsError = "InsufficientPermissions";
|
||||
internal const string SetTimeZoneFailedError = "SetTimeZoneFailed";
|
||||
|
||||
#endregion Error Ids
|
||||
|
||||
/// <summary>
|
||||
/// Clear the cached TimeZoneInfo data. Note that since TimeZoneInfo.ClearCachedData
|
||||
/// does not exist in .NET Core there is an alternative mechanism (which doesn't seem
|
||||
/// to affect clearing out the cache under the full desktop version of .NET)
|
||||
/// </summary>
|
||||
internal static void ClearCachedData()
|
||||
{
|
||||
#if CORECLR
|
||||
// NOTE: .NET Core does not expose the ClearCachedData method, but executing
|
||||
// the specific ConvertTime request below should have the same effect.
|
||||
TimeZoneInfo.ConvertTime(new DateTime(0), TimeZoneInfo.Local);
|
||||
#else
|
||||
TimeZoneInfo.ClearCachedData();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the system time zone by checking first against StandardName and then,
|
||||
/// if no matches were found, against the DaylightName.
|
||||
/// </summary>
|
||||
/// <param name="name">The name (or wildcard pattern) of the system time zone to find.</param>
|
||||
/// <returns>A TimeZoneInfo object array containing information about the specified system time zones.</returns>
|
||||
internal static TimeZoneInfo[] LookupSystemTimeZoneInfoByName(string name)
|
||||
{
|
||||
WildcardPattern namePattern = new WildcardPattern(name, WildcardOptions.IgnoreCase);
|
||||
List<TimeZoneInfo> tzi = new List<TimeZoneInfo>();
|
||||
|
||||
// get the available system time zones
|
||||
ReadOnlyCollection<TimeZoneInfo> zones = TimeZoneInfo.GetSystemTimeZones();
|
||||
|
||||
// check against the standard and daylight names for each TimeZoneInfo
|
||||
foreach (TimeZoneInfo zone in zones)
|
||||
{
|
||||
if (namePattern.IsMatch(zone.StandardName) || namePattern.IsMatch(zone.DaylightName))
|
||||
{
|
||||
tzi.Add(zone);
|
||||
}
|
||||
}
|
||||
|
||||
return (tzi.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="MultipleMatchingTimeZones" xml:space="preserve">
|
||||
<value>Cannot set the local time zone because the name '{0}' resolves to multiple entries.</value>
|
||||
</data>
|
||||
<data name="TimeZoneNameNotFound" xml:space="preserve">
|
||||
<value>The time zone name '{0}' was not found on the local computer.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,282 @@
|
||||
/********************************************************************++
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
--********************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using Dbg=System.Management.Automation.Diagnostics;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Management.Automation.Provider;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the base class from which all catalog commands are derived.
|
||||
/// </summary>
|
||||
public abstract class CatalogCommandsBase : PSCmdlet
|
||||
{
|
||||
/// <summary>
|
||||
/// Path of folder/file to generate or validate the catalog file
|
||||
/// </summary>
|
||||
[Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByPath")]
|
||||
public string CatalogFilePath
|
||||
{
|
||||
get
|
||||
{
|
||||
return catalogFilePath;
|
||||
}
|
||||
set
|
||||
{
|
||||
catalogFilePath = value;
|
||||
}
|
||||
}
|
||||
private string catalogFilePath;
|
||||
|
||||
/// <summary>
|
||||
/// Path of folder/file to generate or validate the catalog file
|
||||
/// </summary>
|
||||
[Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByPath")]
|
||||
public string[] Path
|
||||
{
|
||||
get
|
||||
{
|
||||
return path;
|
||||
}
|
||||
set
|
||||
{
|
||||
path = value;
|
||||
}
|
||||
}
|
||||
private string[] path;
|
||||
//
|
||||
// name of this command
|
||||
//
|
||||
string commandName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the CatalogCommandsBase class,
|
||||
/// using the given command name.
|
||||
/// </summary>
|
||||
/// <param name="name">
|
||||
/// The name of the command.
|
||||
/// </param>
|
||||
protected CatalogCommandsBase(string name) : base()
|
||||
{
|
||||
commandName = name;
|
||||
}
|
||||
|
||||
private CatalogCommandsBase() : base() {}
|
||||
|
||||
/// <summary>
|
||||
/// Processes records from the input pipeline.
|
||||
/// For each input object, the command either generate the Catalog or
|
||||
/// Validates the existing Catalog
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
//
|
||||
// this cannot happen as we have specified the Path
|
||||
// property to be mandatory parameter
|
||||
//
|
||||
Dbg.Assert((CatalogFilePath != null) && (CatalogFilePath.Length > 0),
|
||||
"CatalogCommands: Param binder did not bind catalogFilePath");
|
||||
|
||||
Collection<string> paths = new Collection<string>();
|
||||
|
||||
if (Path != null)
|
||||
{
|
||||
foreach (string p in Path)
|
||||
{
|
||||
foreach (PathInfo tempPath in SessionState.Path.GetResolvedPSPathFromPSPath(p))
|
||||
{
|
||||
paths.Add(tempPath.ProviderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string drive = null;
|
||||
|
||||
// resolve catalog destination Path
|
||||
if (!SessionState.Path.IsPSAbsolute(catalogFilePath, out drive) && !System.IO.Path.IsPathRooted(catalogFilePath))
|
||||
{
|
||||
catalogFilePath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(catalogFilePath);
|
||||
}
|
||||
|
||||
PerformAction(paths, catalogFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the action i.e. Generate or Validate the Windows Catalog File
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// The name of the Folder or file on which to perform the action.
|
||||
/// </param>
|
||||
/// <param name="catalogFilePath">
|
||||
/// Path to Catalog
|
||||
/// </param>
|
||||
protected abstract void PerformAction(Collection<string> path, string catalogFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the implementation of the 'New-FileCatalog' cmdlet.
|
||||
/// This cmdlet generates the catalog for File or Folder
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.New, "FileCatalog", SupportsShouldProcess = true, DefaultParameterSetName = "ByPath",
|
||||
HelpUri = "http://go.microsoft.com/fwlink/?LinkId=786749")]
|
||||
[OutputType(typeof(FileInfo))]
|
||||
public sealed class NewFileCatalogCommand : CatalogCommandsBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the New-FileCatalog class.
|
||||
/// </summary>
|
||||
public NewFileCatalogCommand() : base("New-FileCatalog") { }
|
||||
|
||||
/// <summary>
|
||||
/// Catalog version
|
||||
/// </summary>
|
||||
[Parameter()]
|
||||
public int CatalogVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return catalogVersion;
|
||||
}
|
||||
set
|
||||
{
|
||||
catalogVersion = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Based on the Catalog version we will decide which hashing Algorithm to use
|
||||
private int catalogVersion = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Generate the Catalog for the Path
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// File or Folder Path
|
||||
/// </param>
|
||||
/// <param name="catalogFilePath">
|
||||
/// Path to Catalog
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if able to Create Catalog or else False
|
||||
/// </returns>
|
||||
protected override void PerformAction(Collection<string> path,string catalogFilePath)
|
||||
{
|
||||
if (path.Count == 0)
|
||||
{
|
||||
// if user has not provided the path use current directory to generate catalog
|
||||
path.Add(SessionState.Path.CurrentFileSystemLocation.Path);
|
||||
}
|
||||
|
||||
FileInfo catalogFileInfo = new FileInfo(catalogFilePath);
|
||||
|
||||
// If Path points to the expected cat file make sure
|
||||
// parent Directory exists other wise CryptoAPI fails to create a .cat file
|
||||
if (catalogFileInfo.Extension.Equals(".cat", StringComparison.Ordinal))
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(catalogFileInfo.Directory.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This only creates Directory if it does not exists, Append a default name
|
||||
System.IO.Directory.CreateDirectory(catalogFilePath);
|
||||
catalogFilePath = System.IO.Path.Combine(catalogFilePath, "catalog.cat");
|
||||
}
|
||||
|
||||
FileInfo catalogFile = CatalogHelper.GenerateCatalog(this, path, catalogFilePath, catalogVersion);
|
||||
|
||||
if (catalogFile != null)
|
||||
{
|
||||
WriteObject(catalogFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the implementation of the 'Test-FileCatalog' cmdlet.
|
||||
/// This cmdlet validaes the Intgerity of catalog
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsDiagnostic.Test, "FileCatalog", SupportsShouldProcess = true, DefaultParameterSetName = "ByPath",
|
||||
HelpUri = "http://go.microsoft.com/fwlink/?LinkId=786750")]
|
||||
[OutputType(typeof(CatalogValidationStatus))]
|
||||
[OutputType(typeof(CatalogInformation))]
|
||||
public sealed class TestFileCatalogCommand : CatalogCommandsBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the New-FileCatalog class.
|
||||
/// </summary>
|
||||
public TestFileCatalogCommand() : base("Test-FileCatalog") { }
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[Parameter()]
|
||||
public SwitchParameter Detailed
|
||||
{
|
||||
get { return detailed; }
|
||||
set { detailed = value; }
|
||||
}
|
||||
private bool detailed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Patterns used to exclude files from DiskPaths and Catalog
|
||||
/// </summary>
|
||||
[Parameter()]
|
||||
public string[] FilesToSkip
|
||||
{
|
||||
get
|
||||
{
|
||||
return filesToSkip;
|
||||
}
|
||||
set
|
||||
{
|
||||
filesToSkip = value;
|
||||
this.excludedPatterns = new WildcardPattern[filesToSkip.Length];
|
||||
for (int i = 0; i < filesToSkip.Length; i++)
|
||||
{
|
||||
this.excludedPatterns[i] = WildcardPattern.Get(filesToSkip[i], WildcardOptions.IgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
string[] filesToSkip = null;
|
||||
internal WildcardPattern[] excludedPatterns = null;
|
||||
|
||||
/// <summary>
|
||||
/// Validate the Integrity of given Catalog
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// File or Folder Path
|
||||
/// </param>
|
||||
/// <param name="catalogFilePath">
|
||||
/// Path to Catalog
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if able to Validate the Catalog and its not tampered or else False
|
||||
/// </returns>
|
||||
protected override void PerformAction(Collection<string> path, string catalogFilePath)
|
||||
{
|
||||
if (path.Count == 0)
|
||||
{
|
||||
// if user has not provided the path use the path of catalog file itself.
|
||||
path.Add(new FileInfo(catalogFilePath).Directory.FullName);
|
||||
}
|
||||
|
||||
CatalogInformation catalogInfo = CatalogHelper.ValidateCatalog(this, path, catalogFilePath, excludedPatterns);
|
||||
|
||||
if (detailed)
|
||||
{
|
||||
WriteObject(catalogInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteObject(catalogInfo.Status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="CatalogDefintionFileNotGenerated" xml:space="preserve">
|
||||
<value>Unable to generate catalog defintion file.</value>
|
||||
</data>
|
||||
<data name="AddFileToCatalog" xml:space="preserve">
|
||||
<value>Adding file '{0}' to the catalog. Relative path of file in catalog is '{1}'.</value>
|
||||
</data>
|
||||
<data name="SkipValidationOfCatalogFile" xml:space="preserve">
|
||||
<value>Skipping validating file {0} from catalog. </value>
|
||||
</data>
|
||||
<data name="FoundFileHashInCatlogItem" xml:space="preserve">
|
||||
<value>Found file {0} in the catalog with hash of {1}. </value>
|
||||
</data>
|
||||
<data name="FoundDuplicateFilesRelativePath" xml:space="preserve">
|
||||
<value>Paths for catalog contains multiple files with same relative path {0}. </value>
|
||||
</data>
|
||||
<data name="FoundFileInPath" xml:space="preserve">
|
||||
<value>Found file {0} on disk with hash of {1}.</value>
|
||||
</data>
|
||||
<data name="SkipValidationOfPathFile" xml:space="preserve">
|
||||
<value>Skipping validating file {0} from path.</value>
|
||||
</data>
|
||||
<data name="UnableToAcquireHashAlgorithmContext" xml:space="preserve">
|
||||
<value>Unable to acquire a handle to a catalog administrator context for a given hash algorithm {0}.</value>
|
||||
</data>
|
||||
<data name="UnableToCreateFileHash" xml:space="preserve">
|
||||
<value>Unable to create the hash for file {0}. </value>
|
||||
</data>
|
||||
<data name="UnableToOpenCatalogFile" xml:space="preserve">
|
||||
<value>Unable to open catalog file {0}.</value>
|
||||
</data>
|
||||
<data name="UnKnownCatalogVersion" xml:space="preserve">
|
||||
<value>Catalog version is not valid. We only support catalog version {0} and version {1}.</value>
|
||||
</data>
|
||||
<data name="UnableToOpenCatalogDefinitionFile" xml:space="preserve">
|
||||
<value>Unable to open catalog defintion file.</value>
|
||||
</data>
|
||||
<data name="FoundDuplicateFileMemberInCatalog" xml:space="preserve">
|
||||
<value>Found multiple entries of file member {0} in the catalog.</value>
|
||||
</data>
|
||||
<data name="UnableToFindFileNameOrPathForCatalogMember" xml:space="preserve">
|
||||
<value>Unable to find file name or path for catalog member {0}.</value>
|
||||
</data>
|
||||
<data name="UnableToFindFileToHash" xml:space="preserve">
|
||||
<value>Unable to find file {0} to hash.</value>
|
||||
</data>
|
||||
<data name="UnableToReadFileToHash" xml:space="preserve">
|
||||
<value>Unable to read file {0} to calculate its hash.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,846 @@
|
||||
|
||||
/********************************************************************++
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
--********************************************************************/
|
||||
|
||||
using Dbg = System.Management.Automation;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management.Automation.Internal;
|
||||
using System.Management.Automation.Provider;
|
||||
using System.Management.Automation.Security;
|
||||
using System.Runtime.InteropServices;
|
||||
using DWORD = System.UInt32;
|
||||
|
||||
namespace System.Management.Automation
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Defines the possible status when validating integrity of catalog
|
||||
/// </summary>
|
||||
public enum CatalogValidationStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Status when catalog is not tampered.
|
||||
/// </summary>
|
||||
Valid,
|
||||
|
||||
/// <summary>
|
||||
/// Status when catalog is tampered.
|
||||
/// </summary>
|
||||
ValidationFailed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object returned by Catalog Cmdlets
|
||||
/// </summary>
|
||||
public class CatalogInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// status of catalog
|
||||
/// </summary>
|
||||
public CatalogValidationStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hash Algorithm used to calculate the hashes of files in Catalog
|
||||
/// </summary>
|
||||
public string HashAlgorithm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary mapping files relative paths to their hash values found from Catalog
|
||||
/// </summary>
|
||||
public Dictionary<String, String> CatalogItems { get; set;}
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary mapping files relative paths to their hash values
|
||||
/// </summary>
|
||||
public Dictionary<String, String> PathItems { get; set;}
|
||||
|
||||
/// <summary>
|
||||
/// Signature for the catalog
|
||||
/// </summary>
|
||||
public Signature Signature { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper functions for Windows Catalog functionality
|
||||
/// </summary>
|
||||
internal static class CatalogHelper
|
||||
{
|
||||
|
||||
// Catalog Version is (0X100 = 256) for Catalog Version 1
|
||||
private static int catalogVersion1 = 256;
|
||||
|
||||
// Catalog Version is (0X200 = 512) for Catalog Version 2
|
||||
private static int catalogVersion2 = 512;
|
||||
|
||||
// Hash Algorithms supported by Windows Catalog
|
||||
private static string HashAlgorithmSHA1 = "SHA1";
|
||||
private static string HashAlgorithmSHA256 = "SHA256";
|
||||
private static PSCmdlet _cmdlet = null;
|
||||
|
||||
/// <summary>
|
||||
/// Find out the Version of Catalog by reading its Meta data. We can have either version 1 or version 2 catalog
|
||||
/// </summary>
|
||||
/// <param name="catalogHandle"> Handle to open catalog file </param>
|
||||
/// <returns> Version of the catalog </returns>
|
||||
private static int GetCatalogVersion(IntPtr catalogHandle)
|
||||
{
|
||||
int catalogVersion = -1;
|
||||
|
||||
IntPtr catalogData = NativeMethods.CryptCATStoreFromHandle(catalogHandle);
|
||||
NativeMethods.CRYPTCATSTORE catalogInfo = ClrFacade.PtrToStructure<NativeMethods.CRYPTCATSTORE>(catalogData);
|
||||
|
||||
if (catalogInfo.dwPublicVersion == catalogVersion2)
|
||||
{
|
||||
catalogVersion = 2;
|
||||
}
|
||||
// One Windows 7 this API sent version information as decimal 1 not hex (0X100 = 256)
|
||||
// so we are checking for that value as well. Reason we are not checking for version 2 above in
|
||||
// this scenario because catalog verion 2 is not supported on win7.
|
||||
else if ((catalogInfo.dwPublicVersion == catalogVersion1) || (catalogInfo.dwPublicVersion == 1))
|
||||
{
|
||||
catalogVersion = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// catalog version we don't understand
|
||||
Exception exception = new InvalidOperationException(StringUtil.Format(CatalogStrings.UnKnownCatalogVersion,
|
||||
catalogVersion1.ToString("X"),
|
||||
catalogVersion2.ToString("X")));
|
||||
|
||||
ErrorRecord errorRecord = new ErrorRecord(exception, "UnKnownCatalogVersion", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
return catalogVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HashAlgorithm used by the Catalog. It is based on the version of Catalog.
|
||||
/// </summary>
|
||||
/// <param name="catalogVersion"> Path of the output catalog file </param>
|
||||
/// <returns> Version of the catalog </returns>
|
||||
private static string GetCatalogHashAlgorithm(int catalogVersion)
|
||||
{
|
||||
string hashAlgorithm = string.Empty;
|
||||
|
||||
if (catalogVersion == 1)
|
||||
{
|
||||
hashAlgorithm = HashAlgorithmSHA1;
|
||||
}
|
||||
else if (catalogVersion == 2)
|
||||
{
|
||||
hashAlgorithm = HashAlgorithmSHA256;
|
||||
}
|
||||
else
|
||||
{
|
||||
// version we don't understand
|
||||
Exception exception = new InvalidOperationException(StringUtil.Format(CatalogStrings.UnKnownCatalogVersion,
|
||||
"1.0",
|
||||
"2.0"));
|
||||
|
||||
ErrorRecord errorRecord = new ErrorRecord(exception, "UnKnownCatalogVersion", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
return hashAlgorithm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate the Catalog Defintion File representing files and folders
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="Path"> Path of expected output .cdf file </param>
|
||||
///
|
||||
/// <param name="catalogFilePath"> Path of the output catalog file </param>
|
||||
///
|
||||
/// <param name="cdfFilePath"> Path of the catalog definiton file </param>
|
||||
///
|
||||
/// <param name="catalogVersion"> Version of catalog</param>
|
||||
///
|
||||
/// <param name="hashAlgorithm"> hash method used to generate hashes for the Catalog </param>
|
||||
/// <returns> HashSet for the relative Path for files in Catalog </returns>
|
||||
internal static string GenerateCDFFile(Collection<string> Path, string catalogFilePath, string cdfFilePath, int catalogVersion, string hashAlgorithm)
|
||||
{
|
||||
HashSet<string> relativePaths = new HashSet<string>();
|
||||
|
||||
string cdfHeaderContent = string.Empty;
|
||||
string cdfFilesContent = string.Empty;
|
||||
int catAttributeCount = 0;
|
||||
|
||||
// First create header and files section for the catalog then write in file
|
||||
cdfHeaderContent += "[CatalogHeader]" + Environment.NewLine;
|
||||
cdfHeaderContent += @"Name=" + catalogFilePath + Environment.NewLine;
|
||||
cdfHeaderContent += "CatalogVersion=" + catalogVersion + Environment.NewLine;
|
||||
cdfHeaderContent += "HashAlgorithms=" + hashAlgorithm + Environment.NewLine;
|
||||
cdfFilesContent += "[CatalogFiles]" + Environment.NewLine;
|
||||
|
||||
foreach (string catalogFile in Path)
|
||||
{
|
||||
if (System.IO.Directory.Exists(catalogFile))
|
||||
{
|
||||
var directoryItems = Directory.EnumerateFiles(catalogFile, "*.*", SearchOption.AllDirectories);
|
||||
foreach (string fileItem in directoryItems)
|
||||
{
|
||||
ProcessFileToBeAddedInCatalogDefinitionFile(new FileInfo(fileItem), new DirectoryInfo(catalogFile), ref relativePaths, ref cdfHeaderContent, ref cdfFilesContent, ref catAttributeCount);
|
||||
}
|
||||
}
|
||||
else if (System.IO.File.Exists(catalogFile))
|
||||
{
|
||||
ProcessFileToBeAddedInCatalogDefinitionFile(new FileInfo(catalogFile), null, ref relativePaths, ref cdfHeaderContent, ref cdfFilesContent, ref catAttributeCount);
|
||||
}
|
||||
}
|
||||
|
||||
using (System.IO.StreamWriter fileWriter = new System.IO.StreamWriter(new FileStream(cdfFilePath, FileMode.Create)))
|
||||
{
|
||||
fileWriter.WriteLine(cdfHeaderContent);
|
||||
fileWriter.WriteLine();
|
||||
fileWriter.WriteLine(cdfFilesContent);
|
||||
}
|
||||
return cdfFilePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file attribute (Relative path in our case) from catalog
|
||||
/// </summary>
|
||||
/// <param name="fileToHash"> file to hash </param>
|
||||
/// <param name="dirInfo"> directory information about file needed to calculate relative file path. </param>
|
||||
/// <param name="relativePaths"> working set of relative paths of all files. </param>
|
||||
/// <param name="cdfHeaderContent"> content to be added in CatalogHeader section of cdf File</param>
|
||||
/// <param name="cdfFilesContent"> content to be added in CatalogFiles section of cdf File </param>
|
||||
/// <param name="catAttributeCount"> indictaing the current no of catalog header level attributes </param>
|
||||
/// <returns> void </returns>
|
||||
internal static void ProcessFileToBeAddedInCatalogDefinitionFile(FileInfo fileToHash, DirectoryInfo dirInfo, ref HashSet<string> relativePaths, ref string cdfHeaderContent, ref string cdfFilesContent, ref int catAttributeCount)
|
||||
{
|
||||
string relativePath = string.Empty;
|
||||
|
||||
if (dirInfo != null)
|
||||
{
|
||||
//Relative path of the file is the path inside the containing folder excluding folder Name
|
||||
relativePath = fileToHash.FullName.Substring(dirInfo.FullName.Length).TrimStart('\\');
|
||||
}
|
||||
else
|
||||
{
|
||||
relativePath = fileToHash.Name;
|
||||
}
|
||||
|
||||
if (!relativePaths.Contains(relativePath))
|
||||
{
|
||||
relativePaths.Add(relativePath);
|
||||
if (fileToHash.Length != 0)
|
||||
{
|
||||
cdfFilesContent += "<HASH>" + fileToHash.FullName + "=" + fileToHash.FullName + Environment.NewLine;
|
||||
cdfFilesContent += "<HASH>" + fileToHash.FullName + "ATTR1=0x10010001:FilePath:" + relativePath + Environment.NewLine;
|
||||
}
|
||||
else
|
||||
{
|
||||
// zero length files are added as catalog level attributes because they can not be hashed
|
||||
cdfHeaderContent += "CATATTR" + (++catAttributeCount) + "=0x10010001:FilePath:" + relativePath + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If Files have same relative paths we can not distinguish them for
|
||||
// Validation. So failing.
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.FoundDuplicateFilesRelativePath, relativePath)), "FoundDuplicateFilesRelativePath", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Generate the Catalog file for Input Catalog Defintion File
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="cdfFilePath"> Path to the Input .cdf file </param>
|
||||
internal static void GenerateCatalogFile(string cdfFilePath)
|
||||
{
|
||||
string pwszFilePath = cdfFilePath;
|
||||
NativeMethods.CryptCATCDFOpenCallBack catOpenCallBack = new NativeMethods.CryptCATCDFOpenCallBack(ParseErrorCallback);
|
||||
|
||||
// Open CDF File
|
||||
IntPtr resultCDF = NativeMethods.CryptCATCDFOpen(pwszFilePath, catOpenCallBack);
|
||||
|
||||
// navigate CDF header and files sections
|
||||
if (resultCDF != IntPtr.Zero)
|
||||
{
|
||||
// First navigate all catalog level attributes entries first, they represent zero size files
|
||||
IntPtr catalogAttr = IntPtr.Zero;
|
||||
do
|
||||
{
|
||||
catalogAttr = NativeMethods.CryptCATCDFEnumCatAttributes(resultCDF, catalogAttr, catOpenCallBack);
|
||||
|
||||
if (catalogAttr != IntPtr.Zero)
|
||||
{
|
||||
string filePath = ProcessFilePathAttributeInCatalog(catalogAttr);
|
||||
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.AddFileToCatalog, filePath, filePath));
|
||||
}
|
||||
} while (catalogAttr != IntPtr.Zero);
|
||||
|
||||
// navigate all the files hash entries in the .cdf file
|
||||
IntPtr memberInfo = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
IntPtr memberFile = IntPtr.Zero;
|
||||
NativeMethods.CryptCATCDFEnumMembersByCDFTagExErrorCallBack memberCallBack = new NativeMethods.CryptCATCDFEnumMembersByCDFTagExErrorCallBack(ParseErrorCallback);
|
||||
string fileName = string.Empty;
|
||||
do
|
||||
{
|
||||
memberFile = NativeMethods.CryptCATCDFEnumMembersByCDFTagEx(resultCDF, memberFile, memberCallBack, ref memberInfo, true, IntPtr.Zero);
|
||||
fileName = Marshal.PtrToStringUni(memberFile);
|
||||
|
||||
if (!String.IsNullOrEmpty(fileName))
|
||||
{
|
||||
|
||||
IntPtr memberAttr = IntPtr.Zero;
|
||||
string fileRelativePath = String.Empty;
|
||||
do
|
||||
{
|
||||
memberAttr = NativeMethods.CryptCATCDFEnumAttributesWithCDFTag(resultCDF, memberFile, memberInfo, memberAttr, memberCallBack);
|
||||
|
||||
if (memberAttr != IntPtr.Zero)
|
||||
{
|
||||
fileRelativePath = ProcessFilePathAttributeInCatalog(memberAttr);
|
||||
if (!String.IsNullOrEmpty(fileRelativePath))
|
||||
{
|
||||
// Found the attribute we are looking for
|
||||
// Filename we read from the above API has <Hash> appended to its name as per CDF file tags convention
|
||||
// Truncating that Information from the string.
|
||||
string itemName = fileName.Substring(6);
|
||||
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.AddFileToCatalog, itemName, fileRelativePath));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (memberAttr != IntPtr.Zero);
|
||||
}
|
||||
} while (fileName != null);
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.CryptCATCDFClose(resultCDF);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are not able to open CDF file we can not continue generating catalog
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(CatalogStrings.UnableToOpenCatalogDefinitionFile), "UnableToOpenCatalogDefinitionFile", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To generate Catalog for the folder
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="Path"> Path to folder or File </param>
|
||||
/// <param name="catalogFilePath"> Catalog File Path </param>
|
||||
/// <param name="catalogVersion"> Catalog File Path </param>
|
||||
/// <param name="cmdlet"> Instance of cmdlet calling this method </param>
|
||||
/// <returns> true if able to generate .cat file or false </returns>
|
||||
internal static FileInfo GenerateCatalog(PSCmdlet cmdlet, Collection<string> Path, string catalogFilePath, int catalogVersion)
|
||||
{
|
||||
_cmdlet = cmdlet;
|
||||
string hashAlgorithm = GetCatalogHashAlgorithm(catalogVersion);
|
||||
|
||||
if (!String.IsNullOrEmpty(hashAlgorithm))
|
||||
{
|
||||
// Generate Path for Catalog Defintion File
|
||||
string cdfFilePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName());
|
||||
cdfFilePath = cdfFilePath + ".cdf";
|
||||
try
|
||||
{
|
||||
cdfFilePath = GenerateCDFFile(Path, catalogFilePath, cdfFilePath, catalogVersion, hashAlgorithm);
|
||||
|
||||
if (!File.Exists(cdfFilePath))
|
||||
{
|
||||
// If we are not able to generate catalog defintion file we can not continue generating catalog
|
||||
// throw PSTraceSource.NewInvalidOperationException("catalog", CatalogStrings.CatalogDefintionFileNotGenerated);
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(CatalogStrings.CatalogDefintionFileNotGenerated), "CatalogDefintionFileNotGenerated", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
|
||||
GenerateCatalogFile(cdfFilePath);
|
||||
if (File.Exists(catalogFilePath))
|
||||
{
|
||||
return new FileInfo(catalogFilePath);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(cdfFilePath);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file attribute (Relative path in our case) from catalog
|
||||
/// </summary>
|
||||
/// <param name="memberAttrInfo"> Pointer to current attribute of catalog member. </param>
|
||||
/// <returns> value of the attribute </returns>
|
||||
internal static string ProcessFilePathAttributeInCatalog(IntPtr memberAttrInfo)
|
||||
{
|
||||
string relativePath = string.Empty;
|
||||
|
||||
NativeMethods.CRYPTCATATTRIBUTE currentMemberAttr = ClrFacade.PtrToStructure<NativeMethods.CRYPTCATATTRIBUTE>(memberAttrInfo);
|
||||
|
||||
// check if this is the attribute we are looking for
|
||||
// catalog generated other way not using New-FileCatalog can have attributes we don't understand
|
||||
if (currentMemberAttr.pwszReferenceTag.Equals("FilePath", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// find the size for the current attribute value and then allocate buffer and copy from byte array
|
||||
int attrValueSize = (int)currentMemberAttr.cbValue;
|
||||
byte[] attrValue = new byte[attrValueSize];
|
||||
Marshal.Copy(currentMemberAttr.pbValue, attrValue, 0, attrValueSize);
|
||||
relativePath = System.Text.Encoding.Unicode.GetString(attrValue);
|
||||
relativePath = relativePath.TrimEnd('\0');
|
||||
}
|
||||
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make a hash for the file
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="filePath"> Path of the file </param>
|
||||
/// <param name="hashAlgorithm"> Used to calculate Hash </param>
|
||||
/// <returns> HashValue for the file </returns>
|
||||
internal static string CalculateFileHash(string filePath, string hashAlgorithm)
|
||||
{
|
||||
string hashValue = string.Empty;
|
||||
IntPtr catAdmin = IntPtr.Zero;
|
||||
|
||||
// To get handle to the hash algorithm to be used to calculate hashes
|
||||
if (!NativeMethods.CryptCATAdminAcquireContext2(ref catAdmin, IntPtr.Zero, hashAlgorithm, IntPtr.Zero, 0))
|
||||
{
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToAcquireHashAlgorithmContext, hashAlgorithm)), "UnableToAcquireHashAlgorithmContext", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
|
||||
DWORD GENERIC_READ = 0x80000000;
|
||||
DWORD OPEN_EXISTING = 3;
|
||||
IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
|
||||
|
||||
// Open the file that is to be hashed for reading and get its handle
|
||||
IntPtr fileHandle = NativeMethods.CreateFile(filePath, GENERIC_READ, 0, 0 , OPEN_EXISTING, 0, IntPtr.Zero);
|
||||
if (fileHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
try
|
||||
{
|
||||
DWORD hashBufferSize = 0;
|
||||
IntPtr hashBuffer = IntPtr.Zero;
|
||||
|
||||
// Call first time to get the size of expected buffer to hold new hash value
|
||||
if (!NativeMethods.CryptCATAdminCalcHashFromFileHandle2(catAdmin, fileHandle, ref hashBufferSize, hashBuffer, 0))
|
||||
{
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToCreateFileHash, filePath)), "UnableToCreateFileHash", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
|
||||
int size = (int)hashBufferSize;
|
||||
hashBuffer = Marshal.AllocHGlobal(size);
|
||||
try
|
||||
{
|
||||
// Call second time to actually get the hash value
|
||||
if (!NativeMethods.CryptCATAdminCalcHashFromFileHandle2(catAdmin, fileHandle, ref hashBufferSize, hashBuffer, 0))
|
||||
{
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToCreateFileHash, filePath)), "UnableToCreateFileHash", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
|
||||
byte[] hashBytes = new byte[size];
|
||||
Marshal.Copy(hashBuffer, hashBytes, 0, size);
|
||||
hashValue = BitConverter.ToString(hashBytes).Replace("-", "");
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (hashBuffer != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(hashBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.CryptCATAdminReleaseContext(catAdmin, 0);
|
||||
NativeMethods.CloseHandle(fileHandle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are not able to open file that is to be hashed we can not continue with catalog validation
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToReadFileToHash, filePath)), "UnableToReadFileToHash", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make list of hashes for given Catalog File
|
||||
/// </summary>
|
||||
/// <param name="catalogFilePath"> Path to the folder having catalog file </param>
|
||||
/// <param name="excludedPatterns"></param>
|
||||
/// <param name="catalogVersion"> The version of input catalog we read from catalog meta data after opening it.</param>
|
||||
/// <returns> Dictionary mapping files relative paths to HashValues </returns>
|
||||
internal static Dictionary<String, String> GetHashesFromCatalog(string catalogFilePath, WildcardPattern[] excludedPatterns,out int catalogVersion)
|
||||
{
|
||||
IntPtr resultCatalog = NativeMethods.CryptCATOpen(catalogFilePath, 0, IntPtr.Zero, 1, 0);
|
||||
IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
|
||||
Dictionary<String, String> catalogHashes = new Dictionary<String, String>(StringComparer.CurrentCultureIgnoreCase);
|
||||
catalogVersion = 0;
|
||||
|
||||
if (resultCatalog != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
try
|
||||
{
|
||||
IntPtr catAttrInfo = IntPtr.Zero;
|
||||
|
||||
// First traverse all catalog level attributes to get information about zero size file.
|
||||
do
|
||||
{
|
||||
catAttrInfo = NativeMethods.CryptCATEnumerateCatAttr(resultCatalog, catAttrInfo);
|
||||
|
||||
// If we found attribute it is a file information retrieve its relative path
|
||||
// and add it to catalog hash collection if its not in excluded files criteria
|
||||
if (catAttrInfo != IntPtr.Zero)
|
||||
{
|
||||
string relativePath = ProcessFilePathAttributeInCatalog(catAttrInfo);
|
||||
if (!String.IsNullOrEmpty(relativePath))
|
||||
{
|
||||
ProcessCatalogFile(relativePath, string.Empty, excludedPatterns, ref catalogHashes);
|
||||
}
|
||||
}
|
||||
} while (catAttrInfo != IntPtr.Zero);
|
||||
|
||||
catalogVersion = GetCatalogVersion(resultCatalog);
|
||||
|
||||
IntPtr memberInfo = IntPtr.Zero;
|
||||
// Next Navigate all members in Catalog files and get their relative paths and hashes
|
||||
do
|
||||
{
|
||||
memberInfo = NativeMethods.CryptCATEnumerateMember(resultCatalog, memberInfo);
|
||||
if (memberInfo != IntPtr.Zero)
|
||||
{
|
||||
NativeMethods.CRYPTCATMEMBER currentMember = ClrFacade.PtrToStructure<NativeMethods.CRYPTCATMEMBER>(memberInfo);
|
||||
NativeMethods.SIP_INDIRECT_DATA pIndirectData = ClrFacade.PtrToStructure<NativeMethods.SIP_INDIRECT_DATA>(currentMember.pIndirectData);
|
||||
|
||||
// For Catalog version 2 CryptoAPI puts hashes of file attributes(relative path in our case) in Catalog as well
|
||||
// We validate those along with file hashes so we are skipping duplicate entries
|
||||
if (!((catalogVersion == 2) && (pIndirectData.DigestAlgorithm.pszObjId.Equals(new Oid("SHA1").Value, StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
string relativePath = String.Empty;
|
||||
IntPtr memberAttrInfo = IntPtr.Zero;
|
||||
do
|
||||
{
|
||||
memberAttrInfo = NativeMethods.CryptCATEnumerateAttr(resultCatalog, memberInfo, memberAttrInfo);
|
||||
|
||||
if (memberAttrInfo != IntPtr.Zero)
|
||||
{
|
||||
relativePath = ProcessFilePathAttributeInCatalog(memberAttrInfo);
|
||||
if (!String.IsNullOrEmpty(relativePath))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (memberAttrInfo != IntPtr.Zero);
|
||||
|
||||
// If we did not find any Relative Path for the item in catalog we should quit
|
||||
// This catalog must not be valid for our use as catalogs generated using New-FileCatalog
|
||||
// always contains relative file Paths
|
||||
if (String.IsNullOrEmpty(relativePath))
|
||||
{
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToOpenCatalogFile, catalogFilePath)), "UnableToOpenCatalogFile", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
|
||||
ProcessCatalogFile(relativePath, currentMember.pwszReferenceTag, excludedPatterns, ref catalogHashes);
|
||||
}
|
||||
}
|
||||
} while (memberInfo != IntPtr.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.CryptCATClose(resultCatalog);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToOpenCatalogFile, catalogFilePath)), "UnableToOpenCatalogFile", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
return catalogHashes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process file in path for its relative paths
|
||||
/// </summary>
|
||||
/// <param name="relativePath"> relative path of file found in catalog </param>
|
||||
/// <param name="fileHash"> hash of file found in catalog. </param>
|
||||
/// <param name="excludedPatterns"> skip file from validation if it matches these patterns </param>
|
||||
/// <param name="catalogHashes"> collection of hashes of catalog </param>
|
||||
/// <returns> void </returns>
|
||||
internal static void ProcessCatalogFile(string relativePath, string fileHash, WildcardPattern[] excludedPatterns, ref Dictionary<String, String> catalogHashes)
|
||||
{
|
||||
// Found the attribute we are looking for
|
||||
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.FoundFileHashInCatlogItem, relativePath, fileHash));
|
||||
|
||||
// Only add the file for validation if it does not meet exclusion criteria
|
||||
if (!CheckExcludedCriteria((new FileInfo(relativePath)).Name, excludedPatterns))
|
||||
{
|
||||
// Add relativePath mapping to hashvalue for each file
|
||||
catalogHashes.Add(relativePath, fileHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Verbose about skipping file from catalog
|
||||
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.SkipValidationOfCatalogFile, relativePath));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Process file in path for its relative paths
|
||||
/// </summary>
|
||||
/// <param name="fileToHash"> file to hash </param>
|
||||
/// <param name="dirInfo"> directory information about file needed to calculate relative file path. </param>
|
||||
/// <param name="hashAlgorithm"> Used to calculate Hash </param>
|
||||
/// <param name="excludedPatterns"> skip file if it matches these patterns </param>
|
||||
/// <param name="fileHashes"> collection of hashes of files </param>
|
||||
/// <returns> void </returns>
|
||||
internal static void ProcessPathFile(FileInfo fileToHash, DirectoryInfo dirInfo, string hashAlgorithm, WildcardPattern[] excludedPatterns, ref Dictionary<String, String> fileHashes)
|
||||
{
|
||||
string relativePath = string.Empty;
|
||||
string exclude = string.Empty;
|
||||
|
||||
if (dirInfo != null)
|
||||
{
|
||||
//Relative path of the file is the path inside the containing folder excluding folder Name
|
||||
relativePath = fileToHash.FullName.Substring(dirInfo.FullName.Length).TrimStart('\\');
|
||||
exclude = fileToHash.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
relativePath = fileToHash.Name;
|
||||
exclude = relativePath;
|
||||
}
|
||||
|
||||
if (!CheckExcludedCriteria(exclude, excludedPatterns))
|
||||
{
|
||||
string fileHash = string.Empty;
|
||||
|
||||
if(fileToHash.Length != 0)
|
||||
{
|
||||
fileHash = CalculateFileHash(fileToHash.FullName, hashAlgorithm);
|
||||
}
|
||||
|
||||
if (!fileHashes.ContainsKey(relativePath))
|
||||
{
|
||||
fileHashes.Add(relativePath, fileHash);
|
||||
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.FoundFileInPath, relativePath, fileHash));
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.FoundDuplicateFilesRelativePath, relativePath)), "FoundDuplicateFilesRelativePath", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Verbose about skipping file from path
|
||||
_cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.SkipValidationOfPathFile, relativePath));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate the hashes of all the files in given folder
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="folderPaths"> Path to folder or File </param>
|
||||
/// <param name="catalogFilePath"> catalog file path it should be skipped when calculating the hashes </param>
|
||||
/// <param name="hashAlgorithm"> Used to calculate Hash </param>
|
||||
/// <param name="excludedPatterns"></param>
|
||||
/// <returns> Dictionary mapping file relative paths to hashes. </returns>
|
||||
internal static Dictionary<String, String> CalculateHashesFromPath(Collection<string> folderPaths, string catalogFilePath, string hashAlgorithm, WildcardPattern[] excludedPatterns)
|
||||
{
|
||||
// Create a HashTable of file Hashes
|
||||
Dictionary<String, String> fileHashes = new Dictionary<String, String>(StringComparer.CurrentCultureIgnoreCase);
|
||||
|
||||
foreach (string folderPath in folderPaths)
|
||||
{
|
||||
if (System.IO.Directory.Exists(folderPath))
|
||||
{
|
||||
var directoryItems = Directory.EnumerateFiles(folderPath, "*.*", SearchOption.AllDirectories);
|
||||
foreach (string fileItem in directoryItems)
|
||||
{
|
||||
// if its the catalog file we are validating we will skip it
|
||||
if (String.Equals(fileItem, catalogFilePath, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
ProcessPathFile(new FileInfo(fileItem), new DirectoryInfo(folderPath), hashAlgorithm, excludedPatterns, ref fileHashes);
|
||||
}
|
||||
}
|
||||
else if (System.IO.File.Exists(folderPath))
|
||||
{
|
||||
ProcessPathFile(new FileInfo(folderPath), null , hashAlgorithm, excludedPatterns, ref fileHashes);
|
||||
}
|
||||
}
|
||||
return fileHashes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare Dictionary objects
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="catalogItems"> Hashes extracted from Catalog </param>
|
||||
///
|
||||
/// <param name="pathItems"> Hashes created from folders path </param>
|
||||
///
|
||||
/// <returns> True if both collections are same </returns>
|
||||
internal static bool CompareDictionaries(Dictionary<String, String> catalogItems, Dictionary<String, String> pathItems)
|
||||
{
|
||||
bool Status = true;
|
||||
|
||||
List<string> relativePathsFromFolder = pathItems.Keys.ToList();
|
||||
List<string> relativePathsFromCatalog = catalogItems.Keys.ToList();
|
||||
|
||||
// Find entires those are not in both list lists. These should be empty lists for success
|
||||
// Hashes in Catalog should be exact similar to the ones from folder
|
||||
List<string> relativePathsNotInFolder = relativePathsFromFolder.Except(relativePathsFromCatalog, StringComparer.CurrentCultureIgnoreCase).ToList();
|
||||
List<string> relativePathsNotInCatalog = relativePathsFromCatalog.Except(relativePathsFromFolder, StringComparer.CurrentCultureIgnoreCase).ToList();
|
||||
|
||||
//Found extra hashes in Folder
|
||||
if ((relativePathsNotInFolder.Count != 0) || (relativePathsNotInCatalog.Count != 0))
|
||||
{
|
||||
Status = false;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<String, String> item in catalogItems)
|
||||
{
|
||||
string catalogHashValue = (string)catalogItems[item.Key];
|
||||
if (pathItems.ContainsKey(item.Key))
|
||||
{
|
||||
string folderHashValue = (string)pathItems[item.Key];
|
||||
if (folderHashValue.Equals(catalogHashValue))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
Status = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status;
|
||||
}
|
||||
/// <summary>
|
||||
/// To Validate the Integrity of Catalog
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="catalogFolders"> Folder for which catalog is created </param>
|
||||
/// <param name="catalogFilePath"> File Name of the Catalog </param>
|
||||
/// <param name="excludedPatterns"></param>
|
||||
/// <param name="cmdlet"> Instance of cmdlet calling this method </param>
|
||||
/// <returns> Information about Catalog </returns>
|
||||
internal static CatalogInformation ValidateCatalog(PSCmdlet cmdlet, Collection<string> catalogFolders, String catalogFilePath, WildcardPattern[] excludedPatterns)
|
||||
{
|
||||
_cmdlet = cmdlet;
|
||||
int catalogVersion = 0;
|
||||
Dictionary<String, String> catalogHashes = GetHashesFromCatalog(catalogFilePath, excludedPatterns, out catalogVersion);
|
||||
string hashAlgorithm = GetCatalogHashAlgorithm(catalogVersion);
|
||||
|
||||
if (!String.IsNullOrEmpty(hashAlgorithm))
|
||||
{
|
||||
Dictionary<String, String> fileHashes = CalculateHashesFromPath(catalogFolders, catalogFilePath, hashAlgorithm, excludedPatterns);
|
||||
CatalogInformation catalog = new CatalogInformation();
|
||||
catalog.CatalogItems = catalogHashes;
|
||||
catalog.PathItems = fileHashes;
|
||||
bool status = CompareDictionaries(catalogHashes, fileHashes);
|
||||
if (status == true)
|
||||
{
|
||||
catalog.Status = CatalogValidationStatus.Valid;
|
||||
}
|
||||
else
|
||||
{
|
||||
catalog.Status = CatalogValidationStatus.ValidationFailed;
|
||||
}
|
||||
catalog.HashAlgorithm = hashAlgorithm;
|
||||
catalog.Signature = SignatureHelper.GetSignature(catalogFilePath, null);
|
||||
return catalog;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if file meets the skip validation criteria
|
||||
/// </summary>
|
||||
/// <param name="filename"></param>
|
||||
/// <param name="excludedPatterns"></param>
|
||||
/// <returns>True if match is found else false</returns>
|
||||
internal static bool CheckExcludedCriteria(string filename, WildcardPattern[] excludedPatterns)
|
||||
{
|
||||
if (excludedPatterns != null)
|
||||
{
|
||||
foreach (WildcardPattern patternItem in excludedPatterns)
|
||||
{
|
||||
if (patternItem.IsMatch(filename))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Call back when error is thrown by catalog API's
|
||||
/// </summary>
|
||||
private static void ParseErrorCallback(DWORD dwErrorArea, DWORD dwLocalError, string pwszLine)
|
||||
{
|
||||
switch (dwErrorArea)
|
||||
{
|
||||
case NativeConstants.CRYPTCAT_E_AREA_HEADER: break;
|
||||
case NativeConstants.CRYPTCAT_E_AREA_MEMBER: break;
|
||||
case NativeConstants.CRYPTCAT_E_AREA_ATTRIBUTE: break;
|
||||
default: break;
|
||||
}
|
||||
switch (dwLocalError)
|
||||
{
|
||||
case NativeConstants.CRYPTCAT_E_CDF_MEMBER_FILE_PATH:
|
||||
{
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToFindFileNameOrPathForCatalogMember, pwszLine)), "UnableToFindFileNameOrPathForCatalogMember", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
break;
|
||||
}
|
||||
case NativeConstants.CRYPTCAT_E_CDF_MEMBER_INDIRECTDATA:
|
||||
{
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToCreateFileHash, pwszLine)), "UnableToCreateFileHash", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
break;
|
||||
}
|
||||
case NativeConstants.CRYPTCAT_E_CDF_MEMBER_FILENOTFOUND:
|
||||
{
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToFindFileToHash, pwszLine)), "UnableToFindFileToHash", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
break;
|
||||
}
|
||||
case NativeConstants.CRYPTCAT_E_CDF_BAD_GUID_CONV: break;
|
||||
case NativeConstants.CRYPTCAT_E_CDF_ATTR_TYPECOMBO: break;
|
||||
case NativeConstants.CRYPTCAT_E_CDF_ATTR_TOOFEWVALUES: break;
|
||||
case NativeConstants.CRYPTCAT_E_CDF_UNSUPPORTED: break;
|
||||
case NativeConstants.CRYPTCAT_E_CDF_DUPLICATE:
|
||||
{
|
||||
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.FoundDuplicateFileMemberInCatalog, pwszLine)), "FoundDuplicateFileMemberInCatalog", ErrorCategory.InvalidOperation, null);
|
||||
_cmdlet.ThrowTerminatingError(errorRecord);
|
||||
break;
|
||||
}
|
||||
case NativeConstants.CRYPTCAT_E_CDF_TAGNOTFOUND: break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user