diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs index 88a2ee2b16..2f5b7539e4 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs @@ -1,3 +1,5 @@ +#if !LINUX + /********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ @@ -7384,3 +7386,5 @@ $result #endregion }//End namespace + +#endif diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs new file mode 100644 index 0000000000..a1f5f20820 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs @@ -0,0 +1,866 @@ +#if !LINUX + +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 +{ + /// + /// A cmdlet to retrieve time zone information. + /// + [Cmdlet(VerbsCommon.Get, "TimeZone", DefaultParameterSetName = "Name", + HelpUri = "http://go.microsoft.com/fwlink/?LinkId=799468")] + [Alias("gtz")] + public class GetTimeZoneCommand : PSCmdlet + { + #region Parameters + + /// + /// A list of the local time zone ids that the cmdlet should look up. + /// + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Id")] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] Id { get; set; } + + /// + /// Specifies that the cmdlet should produce a collection of the + /// TimeZoneInfo objects that are available on the system. + /// + [Parameter(Mandatory = true, ParameterSetName = "ListAvailable")] + public SwitchParameter ListAvailable { get; set; } + + /// + /// A list of the local time zone names that the cmdlet should look up. + /// + [Parameter(Position = 0, ValueFromPipeline = true, ParameterSetName = "Name")] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] Name { get; set; } + + #endregion Parameters + + /// + /// Implementation of the ProcessRecord method for Get-TimeZone + /// + 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); + } + } + } + } + + + /// + /// A cmdlet to set the system's local time zone. + /// + [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 + + /// + /// The name of the local time zone that the system should use. + /// + [Parameter(Mandatory = true, ParameterSetName = "Id", ValueFromPipelineByPropertyName = true)] + public string Id { get; set; } + + /// + /// A TimeZoneInfo object identifying the local time zone that the system should use. + /// + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "InputObject", ValueFromPipeline = true)] + public TimeZoneInfo InputObject { get; set; } + + /// + /// The name of the local time zone that the system should use. + /// + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Name")] + public string Name { get; set; } + + /// + /// Request return of the new local time zone as a TimeZoneInfo object + /// + [Parameter] + public SwitchParameter PassThru { get; set; } + + #endregion Parameters + + /// + /// Implementation of the ProcessRecord method for Get-TimeZone + /// + [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 + + /// + /// True if the current process has access to change the time zone setting. + /// + 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); + } + } + + /// + /// Set the SeTimeZonePrivilege, which controls access to the SetDynamicTimeZoneInformation API + /// + /// Set to true to enable (or false to disable) the privilege. + 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); + } + } + + /// + /// Get the Win32 error code from GetLastError and throw an exception. + /// + protected void ThrowWin32Error() + { + int error = Marshal.GetLastWin32Error(); + throw new Win32Exception(error); + } + + #endregion Helper functions + + #region Win32 interop helper + + internal class NativeMethods + { + /// + /// Private constructor to prevent instantiation + /// + 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 + + /// + /// Used to marshal win32 SystemTime structure to managed code layer + /// + [StructLayout(LayoutKind.Sequential)] + public struct SystemTime + { + /// + /// The year. + /// + [MarshalAs(UnmanagedType.U2)] + public short Year; + /// + /// The month. + /// + [MarshalAs(UnmanagedType.U2)] + public short Month; + /// + /// The day of the week. + /// + [MarshalAs(UnmanagedType.U2)] + public short DayOfWeek; + /// + /// The day of the month. + /// + [MarshalAs(UnmanagedType.U2)] + public short Day; + /// + /// The hour. + /// + [MarshalAs(UnmanagedType.U2)] + public short Hour; + /// + /// The minute. + /// + [MarshalAs(UnmanagedType.U2)] + public short Minute; + /// + /// The second. + /// + [MarshalAs(UnmanagedType.U2)] + public short Second; + /// + /// The millisecond. + /// + [MarshalAs(UnmanagedType.U2)] + public short Milliseconds; + } + + /// + /// Used to marshal win32 DYNAMIC_TIME_ZONE_INFORMATION structure to managed code layer + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct DYNAMIC_TIME_ZONE_INFORMATION + { + /// + /// The current bias for local time translation on this computer, in minutes. + /// + [MarshalAs(UnmanagedType.I4)] + public int Bias; + /// + /// A description for standard time. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + public string StandardName; + /// + /// 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. + /// + public SystemTime StandardDate; + /// + /// The bias value to be used during local time translations that occur during standard time. + /// + [MarshalAs(UnmanagedType.I4)] + public int StandardBias; + /// + /// A description for daylight saving time (DST). + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + public string DaylightName; + /// + /// 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. + /// + public SystemTime DaylightDate; + /// + /// The bias value to be used during local time translations that occur during daylight saving time. + /// + [MarshalAs(UnmanagedType.I4)] + public int DaylightBias; + /// + /// The name of the time zone registry key on the local computer. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x80)] + public string TimeZoneKeyName; + /// + /// Indicates whether dynamic daylight saving time is disabled. + /// + [MarshalAs(UnmanagedType.U1)] + public bool DynamicDaylightTimeDisabled; + } + + /// + /// Used to marshal win32 TIME_ZONE_INFORMATION structure to managed code layer + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct TIME_ZONE_INFORMATION + { + /// + /// The current bias for local time translation on this computer, in minutes. + /// + [MarshalAs(UnmanagedType.I4)] + public int Bias; + /// + /// A description for standard time. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + public string StandardName; + /// + /// 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. + /// + public SystemTime StandardDate; + /// + /// The bias value to be used during local time translations that occur during standard time. + /// + [MarshalAs(UnmanagedType.I4)] + public int StandardBias; + /// + /// A description for daylight saving time (DST). + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + public string DaylightName; + /// + /// 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. + /// + public SystemTime DaylightDate; + /// + /// The bias value to be used during local time translations that occur during daylight saving time. + /// + [MarshalAs(UnmanagedType.I4)] + public int DaylightBias; + } + + /// + /// PInvoke SetDynamicTimeZoneInformation API + /// + /// A DYNAMIC_TIME_ZONE_INFORMATION structure representing the desired local time zone. + /// + [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 + + /// + /// Definition of TOKEN_QUERY constant from Win32 API + /// + public const int TOKEN_QUERY = 0x00000008; + + /// + /// Definition of TOKEN_ADJUST_PRIVILEGES constant from Win32 API + /// + public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; + + /// + /// Definition of SE_PRIVILEGE_ENABLED constant from Win32 API + /// + public const int SE_PRIVILEGE_ENABLED = 0x00000002; + + /// + /// Definition of SE_TIME_ZONE_NAME constant from Win32 API + /// + public const string SE_TIME_ZONE_NAME = "SeTimeZonePrivilege"; //http://msdn.microsoft.com/en-us/library/bb530716(VS.85).aspx + + /// + /// PInvoke GetCurrentProcess API + /// + /// + [DllImport(GetCurrentProcessApiDllName, ExactSpelling = true)] + public static extern IntPtr GetCurrentProcess(); + + /// + /// PInvoke OpenProcessToken API + /// + /// + /// + /// + /// + [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); + + /// + /// PInvoke LookupPrivilegeValue API + /// + /// + /// + /// + /// + [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); + + /// + /// PInvoke PrivilegeCheck API + /// + /// + /// + /// + /// + [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); + + + /// + /// PInvoke AdjustTokenPrivilege API + /// + /// + /// + /// + /// + /// + /// + /// + [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); + + /// + /// PInvoke CloseHandle API + /// + /// + /// + [DllImport(CloseHandleApiDllName, ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + + /// + /// Used to marshal win32 PRIVILEGE_SET structure to managed code layer + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PRIVILEGE_SET + { + public int PrivilegeCount; + public int Control; + public long Luid; + public int Attributes; + } + + /// + /// Used to marshal win32 TOKEN_PRIVILEGES structure to managed code layer + /// + [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 + + /// + /// Definition of WM_SETTINGCHANGE constant from Win32 API + /// + public const int WM_SETTINGCHANGE = 0x001A; + + /// + /// Definition of HWND_BROADCAST constant from Win32 API + /// + public const int HWND_BROADCAST = (-1); + + /// + /// Definition of SMTO_ABORTIFHUNG constant from Win32 API + /// + public const int SMTO_ABORTIFHUNG = 0x0002; + + /// + /// PInvoke SendMessageTimeout API + /// + /// + /// + /// + /// + /// + /// + /// + /// + [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 + } + + + /// + /// static Helper class for working with system time zones. + /// + 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 + + /// + /// 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) + /// + 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 + } + + /// + /// Find the system time zone by checking first against StandardName and then, + /// if no matches were found, against the DaylightName. + /// + /// The name (or wildcard pattern) of the system time zone to find. + /// A TimeZoneInfo object array containing information about the specified system time zones. + internal static TimeZoneInfo[] LookupSystemTimeZoneInfoByName(string name) + { + WildcardPattern namePattern = new WildcardPattern(name, WildcardOptions.IgnoreCase); + List tzi = new List(); + + // get the available system time zones + ReadOnlyCollection 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()); + } + } +} + +#endif diff --git a/src/Microsoft.PowerShell.Commands.Management/map.json b/src/Microsoft.PowerShell.Commands.Management/map.json index 3e0402ac79..95929d0b19 100644 --- a/src/Microsoft.PowerShell.Commands.Management/map.json +++ b/src/Microsoft.PowerShell.Commands.Management/map.json @@ -85,6 +85,8 @@ "monad/src/cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs": "cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs", "monad/src/commands/management/ParsePathCommand.cs": "commands/management/ParsePathCommand.cs", "monad/src/commands/management/CopyPropertyCommand.cs": "commands/management/CopyPropertyCommand.cs", + "monad/src/commands/management/TimeZoneCommands.cs": "commands/management/TimeZoneCommands.cs", + "monad/src/commands/management/resources/TimeZoneResources.resx": "resources/TimeZoneResources.resx", "monad/src/cimSupport/cmdletization/cim/TerminatingErrorTracker.cs": "cimSupport/cmdletization/cim/TerminatingErrorTracker.cs", "monad/src/cimSupport/cmdletization/cim/cimChildJobBase.cs": "cimSupport/cmdletization/cim/cimChildJobBase.cs" } diff --git a/src/Microsoft.PowerShell.Commands.Management/project.json b/src/Microsoft.PowerShell.Commands.Management/project.json index 8f42c2664f..72c13be937 100644 --- a/src/Microsoft.PowerShell.Commands.Management/project.json +++ b/src/Microsoft.PowerShell.Commands.Management/project.json @@ -10,6 +10,14 @@ "allowUnsafe": true }, + "configurations": { + "Linux": { + "buildOptions": { + "define": [ "LINUX" ] + } + } + }, + "dependencies": { "Microsoft.PowerShell.Security": "1.0.0-*" }, @@ -24,7 +32,6 @@ "commands/management/ClearRecycleBinCommand.cs", "commands/management/ControlPanelItemCommand.cs", "commands/management/CommitTransactionCommand.cs", - "commands/management/Computer.cs", "commands/management/Eventlog.cs", "commands/management/GetClipboardCommand.cs", "commands/management/GetTransactionCommand.cs", @@ -42,11 +49,25 @@ "commands/management/WebServiceProxy.cs", "singleshell/installer/MshManagementMshSnapin.cs" ] + }, + "embed": { + "exclude": [ + "resources/EventlogResources.resx", + "resources/TransactionResources.resx", + "resources/WebServiceResources.resx", + "resources/HotFixResources.resx", + "resources/ControlPanelResources.resx", + "resources/WmiResources.resx", + "resources/ManagementMshSnapInResources.resx", + "resources/ClearRecycleBinResources.resx", + "resources/ClipboardResources.resx" + ] } }, "dependencies": { "System.ServiceProcess.ServiceController": "4.1.0", - "System.Net.NameResolution": "4.0.0" + "System.Net.NameResolution": "4.0.0", + "System.Net.Ping": "4.0.0" } }, "net451": { @@ -64,6 +85,11 @@ "exclude": [ "commands/management/ControlPanelItemCommand.cs" ] + }, + "embed": { + "exclude": [ + "resources/ControlPanelResources.resx" + ] } } } diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/TimeZoneResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/TimeZoneResources.resx new file mode 100644 index 0000000000..1118c6c567 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/resources/TimeZoneResources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cannot set the local time zone because the name '{0}' resolves to multiple entries. + + + The time zone name '{0}' was not found on the local computer. + + \ No newline at end of file diff --git a/src/Microsoft.PowerShell.Commands.Utility/project.json b/src/Microsoft.PowerShell.Commands.Utility/project.json index 3086becd3c..5025f4d3b9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/project.json +++ b/src/Microsoft.PowerShell.Commands.Utility/project.json @@ -41,12 +41,16 @@ "commands/utility/ShowCommand/ShowCommandProxy.cs", "commands/utility/WebCmdlet/BasicHtmlWebResponseObject.cs", "commands/utility/WebCmdlet/ContentHelper.cs", + "commands/utility/WebCmdlet/FormObject.cs", + "commands/utility/WebCmdlet/FormObjectCollection.cs", "commands/utility/WebCmdlet/HtmlWebResponseObject.cs", "commands/utility/WebCmdlet/InvokeRestMethodCommand.cs", "commands/utility/WebCmdlet/InvokeWebRequestCommand.cs", "commands/utility/WebCmdlet/JsonObjectTypeResolver.cs", "commands/utility/WebCmdlet/PSUserAgent.cs", "commands/utility/WebCmdlet/StreamHelper.cs", + "commands/utility/WebCmdlet/WebCmdletElementCollection.cs", + "commands/utility/WebCmdlet/WebRequestMethod.cs", "commands/utility/WebCmdlet/WebRequestPSCmdlet.cs", "commands/utility/WebCmdlet/WebRequestSession.cs", "commands/utility/WebCmdlet/WebResponseHelper.cs", @@ -56,6 +60,21 @@ "commands/utility/update-list.cs", "singleshell/installer/MshUtilityMshSnapin.cs" ] + }, + "embed": { + "exclude": [ + "resources/FormatAndOut_out_gridview.resx", + "resources/ImplicitRemotingStrings.resx", + "resources/UtilityMshSnapinResources.resx", + "resources/OutPrinterDisplayStrings.resx", + "resources/TraceCommandStrings.resx", + "resources/UpdateListStrings.resx", + "resources/SendMailMessageStrings.resx", + "resources/ConvertFromStringResources.resx", + "resources/ConvertStringResources.resx", + "resources/FlashExtractStrings.resx", + "resources/ImmutableStrings.resx" + ] } }, "imports": [ "dnxcore50", "portable-net45+win8" ], diff --git a/src/Microsoft.PowerShell.ConsoleHost/project.json b/src/Microsoft.PowerShell.ConsoleHost/project.json index 297cdfa423..7b8e57207e 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/project.json +++ b/src/Microsoft.PowerShell.ConsoleHost/project.json @@ -68,7 +68,18 @@ "netstandard1.6": { "imports": [ "dnxcore50", "portable-net45+win8" ], "buildOptions": { - "define": [ "CORECLR", "PORTABLE" ] + "define": [ "CORECLR" ], + "compile": { + "exclude": [ + "singleshell/installer/EngineInstaller.cs", + "singleshell/installer/MshHostMshSnapin.cs" + ] + }, + "embed": { + "exclude": [ + "resources/HostMshSnapinResources.resx" + ] + } }, "dependencies": { "Newtonsoft.Json": "7.0.1", diff --git a/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/EngineInstaller.cs b/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/EngineInstaller.cs index 3e7971855f..bcc5467e64 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/EngineInstaller.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/EngineInstaller.cs @@ -1,4 +1,3 @@ -#if !PORTABLE /********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ @@ -87,4 +86,3 @@ namespace Microsoft.PowerShell } } } -#endif diff --git a/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/MshHostMshSnapin.cs b/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/MshHostMshSnapin.cs index 0b9a0f65e2..615d5dfc26 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/MshHostMshSnapin.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/MshHostMshSnapin.cs @@ -1,4 +1,3 @@ -#if !PORTABLE /********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ @@ -93,4 +92,3 @@ namespace Microsoft.PowerShell } } } -#endif diff --git a/src/Microsoft.PowerShell.Security/map.json b/src/Microsoft.PowerShell.Security/map.json index 28288e6dd5..5a7c57668f 100644 --- a/src/Microsoft.PowerShell.Security/map.json +++ b/src/Microsoft.PowerShell.Security/map.json @@ -17,5 +17,6 @@ "monad/src/security/ExecutionPolicyCommands.cs": "security/ExecutionPolicyCommands.cs", "monad/src/security/CmsCommands.cs": "security/CmsCommands.cs", "monad/src/security/AclCommands.cs": "security/AclCommands.cs", + "monad/src/security/CatalogCommands.cs": "security/CatalogCommands.cs", "monad/src/security/resources/SignatureCommands.resx": "resources/SignatureCommands.resx" } diff --git a/src/Microsoft.PowerShell.Security/project.json b/src/Microsoft.PowerShell.Security/project.json index 1ad6818a8d..f1795bc00d 100644 --- a/src/Microsoft.PowerShell.Security/project.json +++ b/src/Microsoft.PowerShell.Security/project.json @@ -10,6 +10,14 @@ "allowUnsafe": true }, + "configurations": { + "Linux": { + "buildOptions": { + "define": [ "LINUX" ] + } + } + }, + "dependencies": { "System.Management.Automation": "1.0.0-*" }, @@ -25,6 +33,13 @@ "security/CmsCommands.cs", "singleshell/installer/MshSecurityMshSnapin.cs" ] + }, + "embed": { + "exclude": [ + "resources/CertificateCommands.resx", + "resources/CmsCommands.resx", + "resources/SecurityMshSnapinResources.resx" + ] } } }, diff --git a/src/Microsoft.PowerShell.Security/security/CatalogCommands.cs b/src/Microsoft.PowerShell.Security/security/CatalogCommands.cs new file mode 100644 index 0000000000..76656c6f4f --- /dev/null +++ b/src/Microsoft.PowerShell.Security/security/CatalogCommands.cs @@ -0,0 +1,286 @@ +#if !LINUX + +/********************************************************************++ +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 +{ + /// + /// Defines the base class from which all catalog commands are derived. + /// + public abstract class CatalogCommandsBase : PSCmdlet + { + /// + /// Path of folder/file to generate or validate the catalog file + /// + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByPath")] + public string CatalogFilePath + { + get + { + return catalogFilePath; + } + set + { + catalogFilePath = value; + } + } + private string catalogFilePath; + + /// + /// Path of folder/file to generate or validate the catalog file + /// + [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; + + /// + /// Initializes a new instance of the CatalogCommandsBase class, + /// using the given command name. + /// + /// + /// The name of the command. + /// + protected CatalogCommandsBase(string name) : base() + { + commandName = name; + } + + private CatalogCommandsBase() : base() {} + + /// + /// Processes records from the input pipeline. + /// For each input object, the command either generate the Catalog or + /// Validates the existing Catalog + /// + 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 paths = new Collection(); + + 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); + } + + /// + /// Performs the action i.e. Generate or Validate the Windows Catalog File + /// + /// + /// The name of the Folder or file on which to perform the action. + /// + /// + /// Path to Catalog + /// + protected abstract void PerformAction(Collection path, string catalogFilePath); + } + + /// + /// Defines the implementation of the 'New-FileCatalog' cmdlet. + /// This cmdlet generates the catalog for File or Folder + /// + [Cmdlet(VerbsCommon.New, "FileCatalog", SupportsShouldProcess = true, DefaultParameterSetName = "ByPath", + HelpUri = "http://go.microsoft.com/fwlink/?LinkId=786749")] + [OutputType(typeof(FileInfo))] + public sealed class NewFileCatalogCommand : CatalogCommandsBase + { + /// + /// Initializes a new instance of the New-FileCatalog class. + /// + public NewFileCatalogCommand() : base("New-FileCatalog") { } + + /// + /// Catalog version + /// + [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; + + /// + /// Generate the Catalog for the Path + /// + /// + /// File or Folder Path + /// + /// + /// Path to Catalog + /// + /// + /// True if able to Create Catalog or else False + /// + protected override void PerformAction(Collection 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); + } + } + } + + /// + /// Defines the implementation of the 'Test-FileCatalog' cmdlet. + /// This cmdlet validaes the Intgerity of catalog + /// + [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 + { + /// + /// Initializes a new instance of the New-FileCatalog class. + /// + public TestFileCatalogCommand() : base("Test-FileCatalog") { } + + /// + /// + [Parameter()] + public SwitchParameter Detailed + { + get { return detailed; } + set { detailed = value; } + } + private bool detailed = false; + + /// + /// Patterns used to exclude files from DiskPaths and Catalog + /// + [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; + + /// + /// Validate the Integrity of given Catalog + /// + /// + /// File or Folder Path + /// + /// + /// Path to Catalog + /// + /// + /// True if able to Validate the Catalog and its not tampered or else False + /// + protected override void PerformAction(Collection 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); + } + } + } + +} + +#endif diff --git a/src/System.Management.Automation/map.json b/src/System.Management.Automation/map.json index 686728edb2..75cdaf758b 100644 --- a/src/System.Management.Automation/map.json +++ b/src/System.Management.Automation/map.json @@ -743,6 +743,8 @@ "monad/src/namespaces/Buffer.cs": "namespaces/Buffer.cs", "monad/src/engine/interpreter/LocalAccess.cs": "engine/interpreter/LocalAccess.cs", "monad/src/security/SecuritySupport.cs": "security/SecuritySupport.cs", + "monad/src/security/CatalogHelper.cs": "security/CatalogHelper.cs", + "monad/src/engine/resources/CatalogStrings.resx": "resources/CatalogStrings.resx", "monad/src/engine/remoting/client/remotepipeline.cs": "engine/remoting/client/remotepipeline.cs", "monad/src/engine/remoting/common/remotingexceptions.cs": "engine/remoting/common/remotingexceptions.cs", "monad/src/help/BaseCommandHelpInfo.cs": "help/BaseCommandHelpInfo.cs", diff --git a/src/System.Management.Automation/project.json b/src/System.Management.Automation/project.json index 4ea22be7f2..fad49661a2 100644 --- a/src/System.Management.Automation/project.json +++ b/src/System.Management.Automation/project.json @@ -125,6 +125,12 @@ "utils/perfCounters/CounterSetRegistrarBase.cs", "utils/perfCounters/PSPerfCountersMgr.cs" ] + }, + "embed": { + "exclude": [ + "resources/CoreMshSnapinResources.resx", + "resources/ErrorPackageRemoting.resx" + ] } }, "dependencies": { @@ -164,6 +170,11 @@ } }, "net451": { + "buildOptions": { + "embed": { + "exclude": [ "resources/CoreClrStubResources.resx" ] + } + }, "frameworkAssemblies": { "System.Runtime": "", "System.Xml": "", diff --git a/src/System.Management.Automation/resources/CatalogStrings.resx b/src/System.Management.Automation/resources/CatalogStrings.resx new file mode 100644 index 0000000000..a9d5706f5e --- /dev/null +++ b/src/System.Management.Automation/resources/CatalogStrings.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Unable to generate catalog defintion file. + + + Adding file '{0}' to the catalog. Relative path of file in catalog is '{1}'. + + + Skipping validating file {0} from catalog. + + + Found file {0} in the catalog with hash of {1}. + + + Paths for catalog contains multiple files with same relative path {0}. + + + Found file {0} on disk with hash of {1}. + + + Skipping validating file {0} from path. + + + Unable to acquire a handle to a catalog administrator context for a given hash algorithm {0}. + + + Unable to create the hash for file {0}. + + + Unable to open catalog file {0}. + + + Catalog version is not valid. We only support catalog version {0} and version {1}. + + + Unable to open catalog defintion file. + + + Found multiple entries of file member {0} in the catalog. + + + Unable to find file name or path for catalog member {0}. + + + Unable to find file {0} to hash. + + + Unable to read file {0} to calculate its hash. + + \ No newline at end of file diff --git a/src/System.Management.Automation/security/CatalogHelper.cs b/src/System.Management.Automation/security/CatalogHelper.cs new file mode 100644 index 0000000000..d3b65f5566 --- /dev/null +++ b/src/System.Management.Automation/security/CatalogHelper.cs @@ -0,0 +1,848 @@ +#if !LINUX + +/********************************************************************++ +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 +{ + + /// + /// Defines the possible status when validating integrity of catalog + /// + public enum CatalogValidationStatus + { + /// + /// Status when catalog is not tampered. + /// + Valid, + + /// + /// Status when catalog is tampered. + /// + ValidationFailed + } + + /// + /// Object returned by Catalog Cmdlets + /// + public class CatalogInformation + { + /// + /// status of catalog + /// + public CatalogValidationStatus Status { get; set; } + + /// + /// Hash Algorithm used to calculate the hashes of files in Catalog + /// + public string HashAlgorithm { get; set; } + + /// + /// Dictionary mapping files relative paths to their hash values found from Catalog + /// + public Dictionary CatalogItems { get; set;} + + /// + /// Dictionary mapping files relative paths to their hash values + /// + public Dictionary PathItems { get; set;} + + /// + /// Signature for the catalog + /// + public Signature Signature { get; set; } + } + + /// + /// Helper functions for Windows Catalog functionality + /// + 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; + + /// + /// Find out the Version of Catalog by reading its Meta data. We can have either version 1 or version 2 catalog + /// + /// Handle to open catalog file + /// Version of the catalog + private static int GetCatalogVersion(IntPtr catalogHandle) + { + int catalogVersion = -1; + + IntPtr catalogData = NativeMethods.CryptCATStoreFromHandle(catalogHandle); + NativeMethods.CRYPTCATSTORE catalogInfo = ClrFacade.PtrToStructure(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; + } + + /// + /// HashAlgorithm used by the Catalog. It is based on the version of Catalog. + /// + /// Path of the output catalog file + /// Version of the catalog + 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; + } + + /// + /// Generate the Catalog Defintion File representing files and folders + /// + /// + /// Path of expected output .cdf file + /// + /// Path of the output catalog file + /// + /// Path of the catalog definiton file + /// + /// Version of catalog + /// + /// hash method used to generate hashes for the Catalog + /// HashSet for the relative Path for files in Catalog + internal static string GenerateCDFFile(Collection Path, string catalogFilePath, string cdfFilePath, int catalogVersion, string hashAlgorithm) + { + HashSet relativePaths = new HashSet(); + + 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; + } + + /// + /// Get file attribute (Relative path in our case) from catalog + /// + /// file to hash + /// directory information about file needed to calculate relative file path. + /// working set of relative paths of all files. + /// content to be added in CatalogHeader section of cdf File + /// content to be added in CatalogFiles section of cdf File + /// indictaing the current no of catalog header level attributes + /// void + internal static void ProcessFileToBeAddedInCatalogDefinitionFile(FileInfo fileToHash, DirectoryInfo dirInfo, ref HashSet 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 += "" + fileToHash.FullName + "=" + fileToHash.FullName + Environment.NewLine; + cdfFilesContent += "" + 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); + } + } + /// + /// Generate the Catalog file for Input Catalog Defintion File + /// + /// + /// Path to the Input .cdf file + 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 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); + } + } + + /// + /// To generate Catalog for the folder + /// + /// + /// Path to folder or File + /// Catalog File Path + /// Catalog File Path + /// Instance of cmdlet calling this method + /// true if able to generate .cat file or false + internal static FileInfo GenerateCatalog(PSCmdlet cmdlet, Collection 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; + } + + /// + /// Get file attribute (Relative path in our case) from catalog + /// + /// Pointer to current attribute of catalog member. + /// value of the attribute + internal static string ProcessFilePathAttributeInCatalog(IntPtr memberAttrInfo) + { + string relativePath = string.Empty; + + NativeMethods.CRYPTCATATTRIBUTE currentMemberAttr = ClrFacade.PtrToStructure(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; + } + + /// + /// Make a hash for the file + /// + /// + /// Path of the file + /// Used to calculate Hash + /// HashValue for the file + 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; + } + + /// + /// Make list of hashes for given Catalog File + /// + /// Path to the folder having catalog file + /// + /// The version of input catalog we read from catalog meta data after opening it. + /// Dictionary mapping files relative paths to HashValues + internal static Dictionary 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 catalogHashes = new Dictionary(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(memberInfo); + NativeMethods.SIP_INDIRECT_DATA pIndirectData = ClrFacade.PtrToStructure(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; + } + + /// + /// Process file in path for its relative paths + /// + /// relative path of file found in catalog + /// hash of file found in catalog. + /// skip file from validation if it matches these patterns + /// collection of hashes of catalog + /// void + internal static void ProcessCatalogFile(string relativePath, string fileHash, WildcardPattern[] excludedPatterns, ref Dictionary 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)); + } + } + /// + /// Process file in path for its relative paths + /// + /// file to hash + /// directory information about file needed to calculate relative file path. + /// Used to calculate Hash + /// skip file if it matches these patterns + /// collection of hashes of files + /// void + internal static void ProcessPathFile(FileInfo fileToHash, DirectoryInfo dirInfo, string hashAlgorithm, WildcardPattern[] excludedPatterns, ref Dictionary 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)); + } + } + + /// + /// Generate the hashes of all the files in given folder + /// + /// + /// Path to folder or File + /// catalog file path it should be skipped when calculating the hashes + /// Used to calculate Hash + /// + /// Dictionary mapping file relative paths to hashes. + internal static Dictionary CalculateHashesFromPath(Collection folderPaths, string catalogFilePath, string hashAlgorithm, WildcardPattern[] excludedPatterns) + { + // Create a HashTable of file Hashes + Dictionary fileHashes = new Dictionary(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; + } + + /// + /// Compare Dictionary objects + /// + /// + /// Hashes extracted from Catalog + /// + /// Hashes created from folders path + /// + /// True if both collections are same + internal static bool CompareDictionaries(Dictionary catalogItems, Dictionary pathItems) + { + bool Status = true; + + List relativePathsFromFolder = pathItems.Keys.ToList(); + List 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 relativePathsNotInFolder = relativePathsFromFolder.Except(relativePathsFromCatalog, StringComparer.CurrentCultureIgnoreCase).ToList(); + List relativePathsNotInCatalog = relativePathsFromCatalog.Except(relativePathsFromFolder, StringComparer.CurrentCultureIgnoreCase).ToList(); + + //Found extra hashes in Folder + if ((relativePathsNotInFolder.Count != 0) || (relativePathsNotInCatalog.Count != 0)) + { + Status = false; + } + + foreach (KeyValuePair 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; + } + /// + /// To Validate the Integrity of Catalog + /// + /// + /// Folder for which catalog is created + /// File Name of the Catalog + /// + /// Instance of cmdlet calling this method + /// Information about Catalog + internal static CatalogInformation ValidateCatalog(PSCmdlet cmdlet, Collection catalogFolders, String catalogFilePath, WildcardPattern[] excludedPatterns) + { + _cmdlet = cmdlet; + int catalogVersion = 0; + Dictionary catalogHashes = GetHashesFromCatalog(catalogFilePath, excludedPatterns, out catalogVersion); + string hashAlgorithm = GetCatalogHashAlgorithm(catalogVersion); + + if (!String.IsNullOrEmpty(hashAlgorithm)) + { + Dictionary 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; + } + + /// + /// Check if file meets the skip validation criteria + /// + /// + /// + /// True if match is found else false + internal static bool CheckExcludedCriteria(string filename, WildcardPattern[] excludedPatterns) + { + if (excludedPatterns != null) + { + foreach (WildcardPattern patternItem in excludedPatterns) + { + if (patternItem.IsMatch(filename)) + { + return true; + } + } + } + return false; + } + /// + /// Call back when error is thrown by catalog API's + /// + 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; + } + } + } +} + +#endif diff --git a/test/map.json b/test/map.json new file mode 100644 index 0000000000..9a1176fc13 --- /dev/null +++ b/test/map.json @@ -0,0 +1,4 @@ +{ + "monad/tests/MultiMachine/Tests/Commands/Cmdlets/Microsoft.PowerShell.Management/Pester.Commands.Cmdlets.Management.TimeZone.Tests.ps1": "powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1", + "monad/tests/MultiMachine/Tests/Commands/Cmdlets/Microsoft.PowerShell.Security/Pester.Commands.Cmdlets.CatalogTests.ps1": "powershell/Modules/Microsoft.PowerShell.Security/FileCatalog.Tests.ps1" +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Clear-EventLog.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Clear-EventLog.Tests.ps1 index 014b3126a7..9fff93ab30 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Clear-EventLog.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Clear-EventLog.Tests.ps1 @@ -1,4 +1,4 @@ -if ($IsWindows) { +if ($IsWindows -and !$IsCore) { #check to see whether we're running as admin in Windows... $windowsIdentity = [Security.Principal.WindowsIdentity]::GetCurrent() $windowsPrincipal = new-object 'Security.Principal.WindowsPrincipal' $windowsIdentity @@ -28,4 +28,4 @@ if ($IsWindows) { } catch {$_.FullyQualifiedErrorId | Should Be "Microsoft.PowerShell.Commands.ClearEventLogCommand"} } } -} \ No newline at end of file +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-EventLog.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-EventLog.Tests.ps1 index d10fcc5943..ebeb00f480 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-EventLog.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-EventLog.Tests.ps1 @@ -1,4 +1,4 @@ -if ($IsWindows) { +if ($IsWindows -and !$IsCore) { #check to see whether we're running as admin in Windows... $windowsIdentity = [Security.Principal.WindowsIdentity]::GetCurrent() $windowsPrincipal = new-object 'Security.Principal.WindowsPrincipal' $windowsIdentity diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/New-EventLog.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/New-EventLog.Tests.ps1 index 8ef155616e..30955ffd06 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/New-EventLog.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/New-EventLog.Tests.ps1 @@ -1,4 +1,4 @@ -if ($IsWindows) { +if ($IsWindows -and !$IsCore) { #check to see whether we're running as admin in Windows... $windowsIdentity = [Security.Principal.WindowsIdentity]::GetCurrent() $windowsPrincipal = new-object 'Security.Principal.WindowsPrincipal' $windowsIdentity @@ -44,4 +44,4 @@ $result.EventID | Should be 4 } } -} \ No newline at end of file +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Remove-EventLog.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Remove-EventLog.Tests.ps1 index 78a6a9ae9f..26787c206d 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Remove-EventLog.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Remove-EventLog.Tests.ps1 @@ -1,4 +1,4 @@ -if ($IsWindows) { +if ($IsWindows -and !$IsCore) { #check to see whether we're running as admin in Windows... $windowsIdentity = [Security.Principal.WindowsIdentity]::GetCurrent() $windowsPrincipal = new-object 'Security.Principal.WindowsPrincipal' $windowsIdentity @@ -28,4 +28,4 @@ } catch {$_.FullyQualifiedErrorId | Should Be "System.InvalidOperationException,Microsoft.PowerShell.Commands.GetEventLogCommand"} } } -} \ No newline at end of file +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1 new file mode 100644 index 0000000000..ae2b340e50 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1 @@ -0,0 +1,227 @@ +# This is a Pester test suite to validate the cmdlets in the TimeZone module +# +# Copyright (c) Microsoft Corporation, 2016 + +<# + -------------------------------------- + Script Notes: opportunities for improving this test script + -------------------------------------- + Localization + Many of the tests below looking-up timezones by Name do not support localization. + That is, the current tests use us english versions of StandardName and DaylighName for tests. + + ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ms725481.aspx + [snippet] Both StandardName and DaylightName are localized according to the current user default UI language. +#> + +if ($IsWindows) { + +function Assert-ListsSame +{ + param([object[]] $expected, [object[]] $observed ) + $compResult = Compare-Object $observed $expected | Select-Object -ExpandProperty InputObject + if ($compResult) + { + $observedList = ([string]::Join("|",$observed)) + $expectedList = ([string]::Join("|",$expected)) + $observedList | Should Be $expectedList + } +} + +Describe "Get-Timezone test case no switches" -tags 'BVT' { + It "Call without ListAvailable switch returns current TimeZoneInfo" { + $observed = (Get-TimeZone).Id + $expected = ([System.TimeZoneInfo]::Local).Id + $observed -eq $expected | Should Be $true + } +} + +Describe "Get-Timezone test cases" -tags 'Innerloop','RI' { + + It "Call without ListAvailable switch returns an object of type TimeZoneInfo" { + $result = (Get-TimeZone).GetType().Name + $result | Should Be "TimeZoneInfo" + } + + It "Call WITH ListAvailable switch returns ArrayList of TimeZoneInfo objects where the list is greater than 0 item" { + $list = Get-TimeZone -ListAvailable + $list.Count -gt 0 | Should Be $true + + $list.GetType().Name | Should Be "Object[]" + $list[0].GetType().Name | Should Be "TimeZoneInfo" + } + + It "Call with ListAvailable switch returns a list containing TimeZoneInfo.Local" { + $observedIdList = Get-TimeZone -ListAvailable | select -ExpandProperty Id + $oneExpectedId = ([System.TimeZoneInfo]::Local).Id + $observedIdList -contains $oneExpectedId | Should Be $true + } + + It "Call with ListAvailable switch returns a list containing one returned by Get-TimeZone" { + $observedIdList = Get-TimeZone -ListAvailable | select -ExpandProperty Id + $oneExpectedId = (Get-TimeZone).Id + $observedIdList -contains $oneExpectedId | Should Be $true + } + + It "Call Get-TimeZone using ID param and single item" { + (Get-TimeZone -Id "Cape Verde Standard Time").Id -eq "Cape Verde Standard Time" | Should Be $true + } + + It "Call Get-TimeZone using ID param and multiple items" { + $idList = @("Cape Verde Standard Time","Morocco Standard Time","Azores Standard Time") + $result = (Get-TimeZone -Id $idList).Id + Assert-ListsSame $result $idList + } + + It "Call Get-TimeZone using ID param and multiple items, where first and third are invalid ids - expect error" { + $null = Get-TimeZone -Id @("Cape Verde Standard","Morocco Standard Time","Azores Standard") ` + -ErrorVariable errVar -ErrorAction SilentlyContinue + $errVar.Count -eq 2 | Should Be $true + $errVar[0].FullyQualifiedErrorID | Should Be "TimeZoneNotFound,Microsoft.PowerShell.Commands.GetTimeZoneCommand" + } + + It "Call Get-TimeZone using ID param and multiple items, one is wild card but error action ignore works as expected" { + $result = Get-TimeZone -Id @("Cape Verde Standard Time","Morocco Standard Time","*","Azores Standard Time") ` + -ErrorAction SilentlyContinue | % Id + $expectedIdList = @("Cape Verde Standard Time","Morocco Standard Time","Azores Standard Time") + Assert-ListsSame $expectedIdList $result + } + + It "Call Get-TimeZone using Name param and singe item" { + $timezoneList = Get-TimeZone -ListAvailable + $timezoneName = $timezoneList[0].StandardName + $observed = Get-TimeZone -Name $timezoneName + $observed.StandardName -eq $timezoneName | Should Be $true + } + + It "Call Get-TimeZone using Name param with wild card" { + $result = (Get-TimeZone -Name "Pacific*").Id + $expectedIdList = @("Pacific Standard Time (Mexico)","Pacific Standard Time","Pacific SA Standard Time") + Assert-ListsSame $expectedIdList $result + } + + It "Verify that alias 'gtz' exists" { + (Get-Alias -Name "gtz").Name | Should Be "gtz" + } + + It "Call Get-TimeZone Name parameter from pipeline by value " { + $result = ("Pacific*" | Get-TimeZone).Id + $expectedIdList = @("Pacific Standard Time (Mexico)","Pacific Standard Time","Pacific SA Standard Time") + Assert-ListsSame $expectedIdList $result + } + + It "Call Get-TimeZone Id parameter from pipeline by ByPropertyName" { + $timezoneList = Get-TimeZone -ListAvailable + $timezone = $timezoneList[0] + $observed = $timezone | Get-TimeZone + $observed.StandardName -eq $timezone.StandardName | Should Be $true + } +} + +Describe "Set-Timezone test case: call by single Id" -tags 'BVT' { + $originalTimeZoneId + BeforeAll { + $originalTimeZoneId = (Get-TimeZone).Id + } + AfterAll { + Set-TimeZone -ID $originalTimeZoneId + } + + It "Call Set-TimeZone by Id" { + $origTimeZoneID = (Get-TimeZone).Id + $timezoneList = Get-TimeZone -ListAvailable + $testTimezone = $null + foreach($timezone in $timezoneList) + { + if ($timezone.Id -ne $origTimeZoneID) + { + $testTimezone = $timezone + break + } + } + Set-TimeZone -Id $testTimezone.Id + $observed = Get-TimeZone + $testTimezone.Id -eq $observed.Id | Should Be $true + } +} + +Describe "Set-Timezone test cases" -tags 'Innerloop','RI' { + $originalTimeZoneId + BeforeAll { + $originalTimeZoneId = (Get-TimeZone).Id + } + AfterAll { + Set-TimeZone -ID $originalTimeZoneId + } + + It "Call Set-TimeZone with invalid Id" { + $exception = $null + try { Set-TimeZone -Id "zzInvalidID" } catch { $exception = $_ } + $exception.FullyQualifiedErrorID | Should Be "TimeZoneNotFound,Microsoft.PowerShell.Commands.SetTimeZoneCommand" + } + + It "Call Set-TimeZone by Name" { + $origTimeZoneName = (Get-TimeZone).StandardName + $timezoneList = Get-TimeZone -ListAvailable + $testTimezone = $null + foreach($timezone in $timezoneList) + { + if ($timezone.StandardName -ne $origTimeZoneName) + { + $testTimezone = $timezone + break + } + } + Set-TimeZone -Name $testTimezone.StandardName + $observed = Get-TimeZone + $testTimezone.StandardName -eq $observed.StandardName | Should Be $true + } + + It "Call Set-TimeZone with invalid Name" { + $exception = $null + try { Set-TimeZone -Name "zzINVALID_Name" } catch { $exception = $_ } + $exception.FullyQualifiedErrorID | Should Be "TimeZoneNotFound,Microsoft.PowerShell.Commands.SetTimeZoneCommand" + } + + It "Verify that alias 'stz' exists" { + (Get-Alias -Name "stz").Name | Should Be "stz" + } + + It "Call Set-TimeZone from pipeline input object of type TimeZoneInfo" { + $origTimeZoneID = (Get-TimeZone).Id + $timezoneList = Get-TimeZone -ListAvailable + $testTimezone = $null + foreach($timezone in $timezoneList) + { + if ($timezone.Id -ne $origTimeZoneID) + { + $testTimezone = $timezone + break + } + } + + $testTimezone | Set-TimeZone + $observed = Get-TimeZone + $observed.ID -eq $testTimezone.Id | Should Be $true + } + + It "Call Set-TimeZone from pipeline input object of type TimeZoneInfo, verify supports whatif" { + $origTimeZoneID = (Get-TimeZone).Id + $timezoneList = Get-TimeZone -ListAvailable + $testTimezone = $null + foreach($timezone in $timezoneList) + { + if ($timezone.Id -ne $origTimeZoneID) + { + $testTimezone = $timezone + break + } + } + + Set-TimeZone -Id $testTimezone.Id -WhatIf > $null + $observed = Get-TimeZone + $observed.Id -eq $origTimeZoneID | Should Be $true + } +} + +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/FileCatalog.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/FileCatalog.Tests.ps1 new file mode 100644 index 0000000000..ec08f7f902 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/FileCatalog.Tests.ps1 @@ -0,0 +1,381 @@ +# This is a Pester test suite to validate the New-FileCatalog & Test-FileCatalog cmdlets on PowerShell Core. +# +# Copyright (c) Microsoft Corporation, 2016 +# + +if ($IsWindows) { + +$script:catalogPath = "" + +Describe "Test suite for NewFileCatalogAndTestFileCatalogCmdlets" -Tags "Innerloop","BVT" { + + #compare two hashtables + function CompareHashTables + { + param + ( + $hashTable1, + $hashTable2 + ) + + foreach ($key in $hashTable1.keys) + { + $keyValue1 = $hashTable1["$key"] + if($hashTable2.ContainsKey($key)) + { + $keyValue2 = $hashTable2["$key"] + $keyValue1 | Should Be $keyValue2 + } + else + { + throw "Failed to find the file $keyValue1 for $key in Hashtable" + } + } + } + + BeforeAll { + $testDataPath = "$PSScriptRoot\TestData\CatalogTestData" + } + + Context "NewAndTestCatalogTests PositiveTestCases when validation Succeeds" { + + It "NewFileCatalogFolder" { + + $sourcePath = Join-Path $testDataPath 'UserConfigProv\DSCResources\scriptdsc' + $catalogPath = "$testDataPath\NewFileCatalogFolder.cat" + + try + { + $null = New-FileCatalog -Path $sourcePath -CatalogFilePath $catalogPath -CatalogVersion 1.0 + $result = Test-FileCatalog -Path $sourcePath -CatalogFilePath $catalogPath -Detailed + } + finally + { + Remove-Item $catalogPath -Force -ErrorAction SilentlyContinue + } + + # Validate result properties + $result.Status | Should Be "Valid" + $result.Signature.Status | Should Be "NotSigned" + $result.HashAlgorithm | Should Be "SHA1" + } + + It "NewFileCatalogFolderWithSubFolders" { + + $sourcePath = Join-Path $testDataPath 'UserConfigProv' + # use non existant Path for the directory when .cat file name is specified + $catalogPath = "$testDataPath\OutPutCatalog\NewFileCatalogFolderWithSubFolders.cat" + + try + { + $null = New-FileCatalog -Path $sourcePath -CatalogFilePath $catalogPath + $result = Test-FileCatalog -Path $sourcePath -CatalogFilePath $catalogPath -Detailed + } + finally + { + Remove-Item "$sourcePath\OutPutCatalog" -Force -ErrorAction SilentlyContinue -Recurse + } + + # Validate result properties + $result.Status | Should Be "Valid" + $result.Signature.Status | Should Be "NotSigned" + $result.HashAlgorithm | Should Be "SHA1" + } + + It "NewFileCatalogWithSingleFile" { + + $sourcePath = Join-Path $testDataPath '\CatalogTestFile1.mof' + # use existant Path for the directory when .cat file name is not specified + $catalogPath = $testDataPath + try + { + $null = New-FileCatalog -Path $sourcePath -CatalogFilePath $catalogPath + $result = Test-FileCatalog -Path $sourcePath -CatalogFilePath ($catalogPath + "\catalog.cat") + } + finally + { + Remove-Item "$catalogPath\catalog.cat" -Force -ErrorAction SilentlyContinue + } + + # Validate result properties + $result | Should Be "Valid" + } + + It "NewFileCatalogForFilesThatDoNotSupportEmbeddedSignatures" { + + $expectedPathsAndHashes = @{ "TestImage.gif" = "B0E4B9F0BB21284AA0AF0D525C913420AD73DA6A" ; + "TestFileCatalog.txt" = "925834D22A8AEB8E0A5EAFABC739F8AFAAD3E490" } + + # use non existant Path for the directory when .cat file name is not specified + $catalogPath = "$testDataPath\OutPutCatalog" + + try + { + $null = New-FileCatalog -Path "$testDataPath\TestImage.gif","$testDataPath\TestFileCatalog.txt" -CatalogFilePath $catalogPath -CatalogVersion 1.0 + $result = Test-FileCatalog -Path "$testDataPath\TestImage.gif","$testDataPath\TestFileCatalog.txt" -CatalogFilePath ($catalogPath + "\catalog.cat") -Detailed + } + finally + { + Remove-Item "$catalogPath" -Force -ErrorAction SilentlyContinue -Recurse + } + + $result.Status | Should Be "Valid" + $result.CatalogItems.Count | Should Be 2 + $result.PathItems.Count | Should Be 2 + CompareHashTables $result.CatalogItems $result.PathItems + CompareHashTables $result.CatalogItems $expectedPathsAndHashes + } + + It "NewFileCatalogWithMutipleFoldersAndFiles" -Skip:$true { + + $expectedPathsAndHashes = @{ + "UserConfigProv.psd1" = "748E5486814051DA3DFB79FE8964152727213248" ; + "DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.schema.mof" ="F7CAB050E32CF0C9B2AC2807C4F24D31EFCC8B61"; + "dscresources\UserConfigProviderModVersion3\UserConfigProviderModVersion3.psm1" = "F9DD6B02C7BD0FB98A25BE0D41210B2A2333E139"; + "DSCResources\scriptdsc\scriptDSC.schema.psm1"= "CDBAF85FEDE2E0CD09B1AEA0532010CEFCECBC12"; + "DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.psm1" = "7599777B85B60377B1F3E492C817190090A754A7" + "DSCResources\scriptdsc\scriptdsc.psd1"= "CDDC68AF9B863760A14031772DC9ADDAFD209D80"; + "DSCResources\UserConfigProviderModVersion3\UserConfigProviderModVersion3.schema.mof" ="AFEB46104F506FC64CAB4B0B2A9C6C50622B487A"; + "DSCResources\UserConfigProviderModVersion2\UserConfigProviderModVersion2.psm1"= "60CB9C8AEDA7A64127D34361ED4F30DEAFE37022"; + "DSCResources\UserConfigProviderModVersion2\UserConfigProviderModVersion2.schema.mof" = "E33FBFEA28E9A8FBA793FBC3D8015BCC9A10944B"; + "CatalogTestFile1.mof" = "083B0953D0D70FFF62710F0356FEB86BCE327FE7"; + "CatalogTestFile2.xml" = "E73BB7A0DD9FAC6A8182F67B750D9CA3094490F1" } + + $catalogPath = "$env:TEMP\NewFileCatalogWithMutipleFoldersAndFiles.cat" + $catalogDataPath = @("$testDataPath\UserConfigProv\","$testDataPath\CatalogTestFile1.mof","$testDataPath\CatalogTestFile2.xml") + + try + { + $null =New-FileCatalog -Path $catalogDataPath -CatalogFilePath $catalogPath -CatalogVersion 1.0 + $result = Test-FileCatalog -Path $catalogDataPath -CatalogFilePath $catalogPath -Detailed + } + finally + { + Remove-Item "$catalogPath" -Force -ErrorAction SilentlyContinue + } + + $result.Status | Should Be "Valid" + $result.Signature.Status | Should Be "NotSigned" + $result.HashAlgorithm | Should Be "SHA1" + $result.CatalogItems.Count | Should Be 11 + $result.PathItems.Count | Should Be 11 + + CompareHashTables $result.CatalogItems $result.PathItems + CompareHashTables $result.CatalogItems $expectedPathsAndHashes + } + + It "NewFileCatalogVersion2WithMutipleFoldersAndFiles" -Skip:$true { + + $expectedPathsAndHashes = @{ + "UserConfigProv.psd1" = "9FFE4CA2873CD91CDC9D71362526446ECACDA64D26DEA768E6CE489B84D888E4" ; + "DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.schema.mof" ="517F625CB6C465928586F5C613F768B33C20F477DAF843C179071B8C74B992AA"; + "DSCResources\UserConfigProviderModVersion3\UserConfigProviderModVersion3.psm1" = "0774A539E73B1A480E38CFFE2CF0B8AC46120A0B2E0377E0DE2630031BE83347"; + "DSCResources\scriptdsc\scriptdsc.schema.psm1"= "7DE80DED0F96FA7D34CF34089A1B088E91CD7B1D80251949FC7C78A6308D51C3"; + "DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.psm1" = "EB0310C630EDFDFBDD1D993A636EC9B75BB1F04DF7E7FFE39CF6357679C852C7" + "DSCResources\scriptdsc\scriptdsc.psd1"= "AB8E8D0840D4854CDCDE25058872413AF417FC016BD77FD5EC677BBB7393532B"; + "DSCResources\UserConfigProviderModVersion3\UserConfigProviderModVersion3.schema.mof" ="7163E607F067A3C4F91D3AFF3C466ECA47C0CF84B5F0DDA22B2C0E99929B5E21"; + "DSCResources\UserConfigProviderModVersion2\UserConfigProviderModVersion2.psm1"= "6591FE02528D7FB66F00E09D7F1A025D5D5BAF30A49C5FF1EC562FAE39B38F43"; + "DSCResources\UserConfigProviderModVersion2\UserConfigProviderModVersion2.schema.mof" = "679318201B012CC5936B29C095956B2131FAF828C0CCA4342A5914F721480FB9"; + "CatalogTestFile1.mof" = "7C1885AE5F76F58DAA232A5E962875F90308C3CB8580400EE12F999B4E10F940"; + "CatalogTestFile2.xml" = "00B7DA28CD285F796660D36B77B2EC6054F21A44D5B329EB6BC4EC7687D70B13"; + "TestImage.gif" = "2D938D255D0D6D547747BD21447CF7295318D34D9B4105D04C1C27487D2FF402" } + + + $catalogPath = "$env:TEMP\NewFileCatalogVersion2WithMutipleFoldersAndFiles.cat" + $catalogDataPath = @("$testDataPath\UserConfigProv\","$testDataPath\CatalogTestFile1.mof","$testDataPath\CatalogTestFile2.xml", "$testDataPath\TestImage.gif") + + try + { + $null = New-FileCatalog -Path $catalogDataPath -CatalogFilePath $catalogPath -CatalogVersion 2.0 + $result = Test-FileCatalog -Path $catalogDataPath -CatalogFilePath $catalogPath -Detailed + } + finally + { + Remove-Item "$catalogPath" -Force -ErrorAction SilentlyContinue + } + + $result.Status | Should Be "Valid" + $result.Signature.Status | Should Be "NotSigned" + $result.HashAlgorithm | Should Be "SHA256" + $result.CatalogItems.Count | Should Be 12 + $result.PathItems.Count | Should Be 12 + CompareHashTables $result.CatalogItems $result.PathItems + CompareHashTables $result.CatalogItems $expectedPathsAndHashes + } + + It "NewFileCatalogFolderWhenCatlogFileIsCreatedInsideSameFolder" { + + $catalogPath = "$env:TEMP\UserConfigProv\NewFileCatalogFolderWhenCatlogFileIsCreatedInsideSameFolder.cat" + try + { + copy-item $testDataPath\UserConfigProv $env:temp -Recurse -ErrorAction SilentlyContinue + $null = New-FileCatalog -Path $env:temp\UserConfigProv\ -CatalogFilePath $catalogPath -CatalogVersion 1.0 + $result = Test-FileCatalog -Path $env:temp\UserConfigProv\ -CatalogFilePath $catalogPath + } + finally + { + Remove-Item "$catalogPath" -Force -ErrorAction SilentlyContinue + Remove-Item "$env:temp\UserConfigProv\" -Force -ErrorAction SilentlyContinue -Recurse + } + + $result | Should Be "Valid" + } + + It "NewFileCatalogWithUnicodeCharactersInFileNames" -Skip:$true { + + $expectedPathsAndHashes = @{ + "UserConfigProv.psd1" = "9FFE4CA2873CD91CDC9D71362526446ECACDA64D26DEA768E6CE489B84D888E4" ; + "DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.schema.mof" ="517F625CB6C465928586F5C613F768B33C20F477DAF843C179071B8C74B992AA"; + "DSCResources\UserConfigProviderModVersion3\UserConfigProviderModVersion3.psm1" = "0774A539E73B1A480E38CFFE2CF0B8AC46120A0B2E0377E0DE2630031BE83347"; + "DSCResources\scriptdsc\scriptdsc.schema.psm1"= "7DE80DED0F96FA7D34CF34089A1B088E91CD7B1D80251949FC7C78A6308D51C3"; + "DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.psm1" = "EB0310C630EDFDFBDD1D993A636EC9B75BB1F04DF7E7FFE39CF6357679C852C7" + "DSCResources\scriptdsc\scriptdsc.psd1"= "AB8E8D0840D4854CDCDE25058872413AF417FC016BD77FD5EC677BBB7393532B"; + "DSCResources\UserConfigProviderModVersion3\UserConfigProviderModVersion3.schema.mof" ="7163E607F067A3C4F91D3AFF3C466ECA47C0CF84B5F0DDA22B2C0E99929B5E21"; + "DSCResources\UserConfigProviderModVersion2\UserConfigProviderModVersion2.psm1"= "6591FE02528D7FB66F00E09D7F1A025D5D5BAF30A49C5FF1EC562FAE39B38F43"; + "DSCResources\UserConfigProviderModVersion2\UserConfigProviderModVersion2.schema.mof" = "679318201B012CC5936B29C095956B2131FAF828C0CCA4342A5914F721480FB9"; + "ٿ ڀ ځ ڂ ڃ ڄ څ چ ڇ ڈ ډ ڊ ڋ ڌ ڍ ڎ ڏ ڐ ڑ.txt" = "EFD0AE8FF12C7387D51FFC03259B60E06DA012BF7D3B7B9D3480FAB2864846CE"; + "ɥ ɦ ɧ ɨ ɩ ɪ ɫ ɬ.txt" = "9FB57660EDD8DA898A9F1E7F5A36B8B760B4A21625F9968D87A32A55B3546BF9"} + + # Create Test Files with unicode characters in names and content + $unicodeTempDir = Join-Path -Path $testDataPath -ChildPath "UnicodeTestDir" + $null = New-Item -ItemType Directory -Path $unicodeTempDir -Force + + $null = New-Item -ItemType File -Path "$unicodeTempDir\ɥ ɦ ɧ ɨ ɩ ɪ ɫ ɬ.txt" -Force -ErrorAction SilentlyContinue + $null = Add-Content -Path "$unicodeTempDir\ɥ ɦ ɧ ɨ ɩ ɪ ɫ ɬ.txt" -Value "Testing unicode" + $null = Out-File -FilePath "$unicodeTempDir\ɥ ɦ ɧ ɨ ɩ ɪ ɫ ɬ.txt" -Encoding unicode -InputObject "ɗ ɘ ə ɚ ɛ ɜ ɝ ɞ ɟ ɠ ɡ ɢ ɣ ɤ ɥ ɦ ɧ ɨ ɩ ɪ ɫ ɬ ɭ ɮ ɯ ɰ ɱ ɲ ɳ ɴ ɵ ɶ ɷ ɸ ɹ ɺ ɻ ɼ ɽ ɾ ɿ ʀ ʁ ʂ ʃ ʄ ʅ" -Append + $null = New-Item -ItemType File -Path "$unicodeTempDir\ٿ ڀ ځ ڂ ڃ ڄ څ چ ڇ ڈ ډ ڊ ڋ ڌ ڍ ڎ ڏ ڐ ڑ.txt" -Force -ErrorAction SilentlyContinue + $null = Out-File -FilePath "$unicodeTempDir\ٿ ڀ ځ ڂ ڃ ڄ څ چ ڇ ڈ ډ ڊ ڋ ڌ ڍ ڎ ڏ ڐ ڑ.txt" -Encoding unicode -InputObject "ਅ ਆ ਇ ਈ ਉ ਊ ਏ ਐ ਓ ਔ ਕ ਖ ਗ ਘ ਙ ਚ ਛ ਜ ਝ ਞ ਟ ਠ ਡ ਢ ਣ ਤ ਥ ਦ ਧ ਨ ਪ ਫ ਬ ਭ ਮ ਯ ਰ ਲ ਲ਼ ਵ " -Append + $null = Out-File -FilePath "$unicodeTempDir\ٿ ڀ ځ ڂ ڃ ڄ څ چ ڇ ڈ ډ ڊ ڋ ڌ ڍ ڎ ڏ ڐ ڑ.txt" -Encoding unicode -InputObject "அ ஆ இ ஈ உ ஊ எ ஏ ஐ ஒ ஓ ஔ க ங ச ஜ ஞ ட ண த ந ன ப ம ய ர ற ல ள ழ வ ஷ ஸ ஹ " -Append + + $catalogPath = "$env:TEMP\క ఖ గ ఘ ఙ చ ఛ జ ఝ ఞ.cat" + $catalogDataPath = @("$testDataPath\UserConfigProv\", "$unicodeTempDir\ٿ ڀ ځ ڂ ڃ ڄ څ چ ڇ ڈ ډ ڊ ڋ ڌ ڍ ڎ ڏ ڐ ڑ.txt" ,"$unicodeTempDir\ɥ ɦ ɧ ɨ ɩ ɪ ɫ ɬ.txt") + + try + { + $null = New-FileCatalog -Path $catalogDataPath -CatalogFilePath $catalogPath -CatalogVersion 2.0 + $result = Test-FileCatalog -Path $catalogDataPath -CatalogFilePath $catalogPath -Detailed + } + finally + { + Remove-Item $unicodeTempDir -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item "$catalogPath" -Force -ErrorAction SilentlyContinue + } + + $result.Status | Should Be "Valid" + $result.Signature.Status | Should Be "NotSigned" + $result.HashAlgorithm | Should Be "SHA256" + $result.CatalogItems.Count | Should Be 11 + $result.PathItems.Count | Should Be 11 + CompareHashTables $result.CatalogItems $result.PathItems + CompareHashTables $result.CatalogItems $expectedPathsAndHashes + } + } + + Context "NewAndTestCatalogTests NegativeTestCases when creation or validation Fails"{ + + AfterEach { + Remove-Item "$script:catalogPath" -Force -ErrorAction SilentlyContinue + Remove-Item "$env:temp\UserConfigProv" -Force -Recurse -ErrorAction SilentlyContinue + } + + It "TestCatalogWhenNewFileAddedtoFolderBeforeValidation" { + + $script:catalogPath = "$env:TEMP\TestCatalogWhenNewFileAddedtoFolderBeforeValidation.cat" + $null = New-FileCatalog -Path $testDataPath\UserConfigProv\ -CatalogFilePath $script:catalogPath -CatalogVersion 2.0 + $null = copy-item $testDataPath\UserConfigProv $env:temp -Recurse -ErrorAction SilentlyContinue + $null = New-Item $env:temp\UserConfigProv\DSCResources\NewFile.txt -ItemType File + Add-Content $env:temp\UserConfigProv\DSCResources\NewFile.txt -Value "More Data" -force + $result = Test-FileCatalog -Path $env:temp\UserConfigProv -CatalogFilePath $script:catalogPath -Detailed + + $result.Status | Should Be "ValidationFailed" + $result.CatalogItems.Count | Should Be 9 + $result.PathItems.Count | Should Be 10 + $result.CatalogItems.ContainsKey("DSCResources\NewFile.txt") | Should Be $false + $result.PathItems.ContainsKey("DSCResources\NewFile.txt") | Should Be $true + + # By Skipping the new added file validation will pass + $result = Test-FileCatalog -Path $env:temp\UserConfigProv -CatalogFilePath $script:catalogPath -Detailed -FilesToSkip "NewFile.txt" + $result.Status | Should Be "Valid" + } + + It "TestCatalogWhenNewFileDeletedFromFolderBeforeValidation" { + + $script:catalogPath = "$env:TEMP\TestCatalogWhenNewFileDeletedFromFolderBeforeValidation.cat" + $null = New-FileCatalog -Path $testDataPath\UserConfigProv\ -CatalogFilePath $script:catalogPath -CatalogVersion 1.0 + $null = copy-item $testDataPath\UserConfigProv $env:temp -Recurse -ErrorAction SilentlyContinue + del $env:temp\UserConfigProv\DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.psm1 -force -ErrorAction SilentlyContinue + $result = Test-FileCatalog -Path $env:temp\UserConfigProv -CatalogFilePath $script:catalogPath -Detailed + + $result.Status | Should Be "ValidationFailed" + $result.CatalogItems.Count | Should Be 9 + $result.PathItems.Count | Should Be 8 + $result.CatalogItems.ContainsKey("DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.psm1") | Should Be $true + $result.PathItems.ContainsKey("DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.psm1") | Should Be $false + + # By Skipping the deleted file validation will pass + $result = Test-FileCatalog -Path $env:temp\UserConfigProv -CatalogFilePath $script:catalogPath -Detailed -FilesToSkip "UserConfigProviderModVersion1.psm1" + $result.Status | Should Be "Valid" + } + + It "TestCatalogWhenFileContentModifiedBeforeValidation" { + + $script:catalogPath = "$env:TEMP\TestCatalogWhenFileContentModifiedBeforeValidation.cat" + $null = New-FileCatalog -Path $testDataPath\UserConfigProv\ -CatalogFilePath $script:catalogPath -CatalogVersion 1.0 + $null = copy-item $testDataPath\UserConfigProv $env:temp -Recurse -ErrorAction SilentlyContinue + Add-Content $env:temp\UserConfigProv\DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.psm1 -Value "More Data" -Force + $result = Test-FileCatalog -Path $env:temp\UserConfigProv -CatalogFilePath $script:catalogPath -Detailed + + $result.Status | Should Be "ValidationFailed" + $result.CatalogItems.Count | Should Be 9 + $result.PathItems.Count | Should Be 9 + $catalogHashValue = $result.CatalogItems["DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.psm1"] + $pathHashValue = $result.PathItems["DSCResources\UserConfigProviderModVersion1\UserConfigProviderModVersion1.psm1"] + ($catalogHashValue -eq $pathHashValue) | Should Be $false + + # By Skipping the file with modifed contents validation will pass + $result = Test-FileCatalog -Path $env:temp\UserConfigProv -CatalogFilePath $script:catalogPath -Detailed -FilesToSkip "UserConfigProviderModVersion1.psm1" + $result.Status | Should Be "Valid" + } + } + + Context "TestCatalog Skip Validation Tests"{ + + AfterEach { + Remove-Item "$script:catalogPath" -Force -ErrorAction SilentlyContinue + } + + It "TestCatalogSkipSingleFileDuringValidation" { + + $script:catalogPath = "$env:TEMP\TestCatalogSkipSingleFileDuringValidation.cat" + $null = New-FileCatalog -Path $testDataPath\UserConfigProv\ -CatalogFilePath $script:catalogPath -CatalogVersion 2.0 + $result = Test-FileCatalog -Path $testDataPath\UserConfigProv\ -CatalogFilePath $script:catalogPath -FilesToSkip "scriptdsc.schema" + $result | Should Be "Valid" + } + + It "TestCatalogSkipCertainFileTypeDuringValidation" { + + $script:catalogPath = "$env:TEMP\TestCatalogSkipCertainFileTypeDuringValidation.cat" + $null = New-FileCatalog -Path $testDataPath\UserConfigProv\ -CatalogFilePath $script:catalogPath -CatalogVersion 2.0 + $result = Test-FileCatalog -Path $testDataPath\UserConfigProv\ -CatalogFilePath $script:catalogPath -FilesToSkip "*.mof" + $result | Should Be "Valid" + } + + It "TestCatalogSkipWildCardPatternDuringValidation" { + + $script:catalogPath = "$env:TEMP\TestCatalogSkipWildCardPatternDuringValidation.cat" + $null = New-FileCatalog -Path $testDataPath\UserConfigProv\ -CatalogFilePath $script:catalogPath -CatalogVersion 1.0 + $result = Test-FileCatalog -Path $testDataPath\UserConfigProv\ -CatalogFilePath $script:catalogPath -FilesToSkip "UserConfigProvider*.psm1" + $result | Should Be "Valid" + } + + It "TestCatalogSkipMuitplePattensDuringValidation" { + + $script:catalogPath = "$env:TEMP\TestCatalogSkipMuitplePattensDuringValidation.cat" + $null = New-FileCatalog -Path $testDataPath\UserConfigProv\ -CatalogFilePath $script:catalogPath -CatalogVersion 1.0 + $result = Test-FileCatalog -Path $testDataPath\UserConfigProv\ -CatalogFilePath $script:catalogPath -FilesToSkip "*.psd1","UserConfigProviderModVersion2.psm1","*ModVersion1.schema.mof" + $result | Should Be "Valid" + } + } +} + +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/CatalogTestFile1.mof b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/CatalogTestFile1.mof new file mode 100644 index 0000000000..5ec9506131 Binary files /dev/null and b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/CatalogTestFile1.mof differ diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/CatalogTestFile2.xml b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/CatalogTestFile2.xml new file mode 100644 index 0000000000..5fbfb31e50 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/CatalogTestFile2.xml @@ -0,0 +1,49 @@ + + + + + + No + Linux + CentOS + Fre + 00000000-0000-0000-0000-000000000000 + No + Yes + + $ipAddress + + Yes + + 2001:4898:28:3:215:5dff:feb8:b9b3 + + Linux1 + Workgroup + None + X64 + en-US + Windows 8 Server + X64 + Server DataCenter + None + Unknown + C:\ + No + + + + Basic + + + + + + + Basic + + + + + + + \ No newline at end of file diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/TestFileCatalog.txt b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/TestFileCatalog.txt new file mode 100644 index 0000000000..5c341af14e --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/TestFileCatalog.txt @@ -0,0 +1 @@ +Text File to test catalog \ No newline at end of file diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/TestImage.gif b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/TestImage.gif new file mode 100644 index 0000000000..cac0342888 Binary files /dev/null and b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/TestImage.gif differ diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion1/UserConfigProviderModVersion1.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion1/UserConfigProviderModVersion1.psm1 new file mode 100644 index 0000000000..f53a084f3d --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion1/UserConfigProviderModVersion1.psm1 @@ -0,0 +1,61 @@ + + +# The Get-TargetResource cmdlet is used to fetch the desired state of the DSC managed node through a powershell script. +# This cmdlet executes the user supplied script (i.e., the script is responsible for validating the desired state of the +# DSC managed node). The result of the script execution is in the form of a hashtable containing all the inormation +# gathered from the GetScript execution. +function Get-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $text + ) + + $result = @{ + Text = "Hello from Get!"; + } + $result; + } + + +# The Set-TargetResource cmdlet is used to Set the desired state of the DSC managed node through a powershell script. +# The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the +# DSC managed node). If the DSC managed node requires a restart either during or after the execution of the SetScript, +# the SetScript notifies the PS Infrasturcure by setting the variable $DSCMachineStatus.IsRestartRequired to $true. +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $text + ) + $path = "$env:SystemDrive\dscTestPath\hello1.txt" + New-Item -Path $path -Type File -force + Add-Content -Path $path -Value $text +} + + +# The Test-TargetResource cmdlet is used to validate the desired state of the DSC managed node through a powershell script. +# The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the +# DSC managed node). The result of the script execution should be true if the DSC managed machine is in the desired state +# or else false should be returned. +function Test-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $text + ) + $false +} + diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion1/UserConfigProviderModVersion1.schema.mof b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion1/UserConfigProviderModVersion1.schema.mof new file mode 100644 index 0000000000..dffe8e6438 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion1/UserConfigProviderModVersion1.schema.mof @@ -0,0 +1,8 @@ + +[ClassVersion("1.0.0")] +class UserConfigProviderModVersion1 : OMI_BaseResource +{ + [key] string Text; +}; + + diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion2/UserConfigProviderModVersion2.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion2/UserConfigProviderModVersion2.psm1 new file mode 100644 index 0000000000..4b5b6855f0 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion2/UserConfigProviderModVersion2.psm1 @@ -0,0 +1,62 @@ + + +# The Get-TargetResource cmdlet is used to fetch the desired state of the DSC managed node through a powershell script. +# This cmdlet executes the user supplied script (i.e., the script is responsible for validating the desired state of the +# DSC managed node). The result of the script execution is in the form of a hashtable containing all the inormation +# gathered from the GetScript execution. +function Get-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $text + ) + + $result = @{ + Text = "Hello from Get!"; + } + $result; + } + + +# The Set-TargetResource cmdlet is used to Set the desired state of the DSC managed node through a powershell script. +# The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the +# DSC managed node). If the DSC managed node requires a restart either during or after the execution of the SetScript, +# the SetScript notifies the PS Infrasturcure by setting the variable $DSCMachineStatus.IsRestartRequired to $true. +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $text + ) + + $path = "$env:SystemDrive\dscTestPath\hello2.txt" + New-Item -Path $path -Type File -force + Add-Content -Path $path -Value $text +} + + +# The Test-TargetResource cmdlet is used to validate the desired state of the DSC managed node through a powershell script. +# The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the +# DSC managed node). The result of the script execution should be true if the DSC managed machine is in the desired state +# or else false should be returned. +function Test-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $text + ) + $false +} + diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion2/UserConfigProviderModVersion2.schema.mof b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion2/UserConfigProviderModVersion2.schema.mof new file mode 100644 index 0000000000..936780b59e --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion2/UserConfigProviderModVersion2.schema.mof @@ -0,0 +1,8 @@ + +[ClassVersion("1.0.0")] +class UserConfigProviderModVersion2 : OMI_BaseResource +{ + [key] string Text; +}; + + diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion3/UserConfigProviderModVersion3.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion3/UserConfigProviderModVersion3.psm1 new file mode 100644 index 0000000000..5a596c4e0c --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion3/UserConfigProviderModVersion3.psm1 @@ -0,0 +1,62 @@ + + +# The Get-TargetResource cmdlet is used to fetch the desired state of the DSC managed node through a powershell script. +# This cmdlet executes the user supplied script (i.e., the script is responsible for validating the desired state of the +# DSC managed node). The result of the script execution is in the form of a hashtable containing all the inormation +# gathered from the GetScript execution. +function Get-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $text + ) + + $result = @{ + Text = "Hello from Get!"; + } + $result; + } + + +# The Set-TargetResource cmdlet is used to Set the desired state of the DSC managed node through a powershell script. +# The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the +# DSC managed node). If the DSC managed node requires a restart either during or after the execution of the SetScript, +# the SetScript notifies the PS Infrasturcure by setting the variable $DSCMachineStatus.IsRestartRequired to $true. +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $text + ) + + $path = "$env:SystemDrive\dscTestPath\hello3.txt" + New-Item -Path $path -Type File -force + Add-Content -Path $path -Value $text +} + + +# The Test-TargetResource cmdlet is used to validate the desired state of the DSC managed node through a powershell script. +# The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the +# DSC managed node). The result of the script execution should be true if the DSC managed machine is in the desired state +# or else false should be returned. +function Test-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $text + ) + $false +} + diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion3/UserConfigProviderModVersion3.schema.mof b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion3/UserConfigProviderModVersion3.schema.mof new file mode 100644 index 0000000000..6cd3c3d8be --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion3/UserConfigProviderModVersion3.schema.mof @@ -0,0 +1,8 @@ + +[ClassVersion("1.0.0")] +class UserConfigProviderModVersion3 : OMI_BaseResource +{ + [key] string Text; +}; + + diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/scriptdsc/scriptdsc.psd1 b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/scriptdsc/scriptdsc.psd1 new file mode 100644 index 0000000000..7039825420 Binary files /dev/null and b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/scriptdsc/scriptdsc.psd1 differ diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/scriptdsc/scriptdsc.schema.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/scriptdsc/scriptdsc.schema.psm1 new file mode 100644 index 0000000000..2c58ee1768 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/scriptdsc/scriptdsc.schema.psm1 @@ -0,0 +1,15 @@ + + +configuration scriptdsc +{ + param ($one, $two, $three) + + Import-DscResource -ModuleName UserConfigProv + Log scriptDscLogResource + { + Message = "This is the message from the embedded log resource: one=$one two=$two three=$three" + } + + } + +Export-ModuleMember -Function scriptdsc diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/UserConfigProv.psd1 b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/UserConfigProv.psd1 new file mode 100644 index 0000000000..2bbd83efc5 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/UserConfigProv.psd1 @@ -0,0 +1,12 @@ +@{ + ModuleVersion = '3.0.0.1' + Author = 'fayzas' + CompanyName = 'Microsoft Corporation' + Copyright = '(c) 2013 Microsoft Corporation. All rights reserved.' + Description = 'Hello World!' + PowerShellVersion = '3.0' + CLRVersion = '4.0' + RequiredModules = @() + # NestedModules = @("UserConfigProviderModVersion.psm1") + # FunctionsToExport = @("Get-TargetResource", "Set-TargetResource", "Test-TargetResource") + } \ No newline at end of file diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Compare-Object.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Compare-Object.Tests.ps1 index 6bde8fdd93..73ff7a5c79 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Compare-Object.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Compare-Object.Tests.ps1 @@ -1,6 +1,6 @@ Describe "Compare-Object" { BeforeAll { - $nl = [Environment]::NewLine + $nl = [Environment]::NewLine $content1 = "line 1" + $nl + "line 2" $content2 = "line 1" + $nl + "line 2.1" @@ -12,10 +12,10 @@ Describe "Compare-Object" { $file3 = Join-Path -Path $TestDrive -ChildPath "test3.txt" $file4 = Join-Path -Path $TestDrive -ChildPath "test4.txt" - New-Item $file1 -ItemType file -Value $content1 -Force - New-Item $file2 -ItemType file -Value $content2 -Force - New-Item $file3 -ItemType file -Value $content3 -Force - New-Item $file4 -ItemType file -Value $content4 -Force + $null = New-Item $file1 -ItemType file -Value $content1 -Force + $null = New-Item $file2 -ItemType file -Value $content2 -Force + $null = New-Item $file3 -ItemType file -Value $content3 -Force + $null = New-Item $file4 -ItemType file -Value $content4 -Force } It "Should be able to compare the same object using the referenceObject and differenceObject switches" { @@ -454,4 +454,4 @@ Describe "Compare-Object DRT basic functionality" -Tags DRT{ $result.InputObject | Should Be "refObject" $result.SideIndicator | Should Be "<=" } -} \ No newline at end of file +}