From 857eda29e3432bc3f46335ab35ef8b9cae60654f Mon Sep 17 00:00:00 2001 From: Donald Duck Date: Mon, 15 Feb 2016 12:32:26 -0500 Subject: [PATCH] initial checkin of yaulw (locally) --- .gitignore | 37 + @integrate/AlternateDataStreamWrapper.cs | 70 + @integrate/App.xaml.cs | 839 ++++++++ @integrate/AppState.cs | 161 ++ @integrate/CMDlineHandler.cs | 311 +++ @integrate/CheckActiveTwo.cs | 482 +++++ @integrate/ClickOnceUpdater.cs | 121 ++ @integrate/Component.All/Common.cs | 276 +++ @integrate/Component.All/Common_MediLytec.cs | 62 + @integrate/Component.All/Component.All.csproj | 79 + @integrate/Component.All/ComponentConfig.cs | 223 +++ .../Component.All/Component_Binary_Manager.cs | 277 +++ .../Component.All/Component_Setup_Manager.cs | 213 +++ .../Component.All/EmbeddedComponentConfig.xml | 13 + @integrate/Component.All/GenericInstaller.cs | 328 ++++ @integrate/Component.All/IInstallComponent.cs | 61 + @integrate/Component.All/IManageComponents.cs | 53 + @integrate/Component.All/ISetup.cs | 112 ++ .../Component.All/Properties/AssemblyInfo.cs | 36 + @integrate/HiddenMainWindow.xaml | 7 + @integrate/HiddenMainWindow.xaml.cs | 1456 ++++++++++++++ @integrate/InstallComponent.cs | 158 ++ @integrate/InstallComponent2.cs | 186 ++ @integrate/InstallComponent4.cs | 254 +++ @integrate/Logging.cs | 240 +++ @integrate/MsgBox.cs | 482 +++++ @integrate/README.txt | 12 + @integrate/ResxHelper.cs | 120 ++ @integrate/Settings.cs | 1057 ++++++++++ @integrate/Snippets.txt | 24 + @integrate/SysTray.cs | 238 +++ @integrate/TTimer.cs | 140 ++ .../AlternateDataStreamInfo.cs | 654 +++++++ .../FileStreamAttributes.cs | 32 + .../Trinet.Core.IO.Ntfs/FileStreamType.cs | 56 + @integrate/Trinet.Core.IO.Ntfs/FileSystem.cs | 452 +++++ .../Trinet.Core.IO.Ntfs/SafeHGlobalHandle.cs | 118 ++ .../Trinet.Core.IO.Ntfs/SafeNativeMethods.cs | 478 +++++ @integrate/Trinet.Core.IO.Ntfs/StreamName.cs | 134 ++ @integrate/WCFHost.cs | 322 ++++ Assembly/AssemblyW.cs | 491 +++++ Components/ICSharpCode.SharpZipLib.dll | Bin 0 -> 200704 bytes Components/log4net.dll | Bin 0 -> 270336 bytes Encryption/TextEncryptHash.cs | 29 + File/CHMFile.cs | 287 +++ File/FileWriter.cs | 193 ++ File/INIFile.cs | 213 +++ File/ISReadWrite.cs | 185 ++ File/LineReplacer.cs | 172 ++ File/LoggerQuick.cs | 176 ++ File/Logging.cs | 391 ++++ File/Tail.cs | 190 ++ Installer/Common.cs | 344 ++++ Installer/FileIcon.cs | 249 +++ Installer/ShellLink.cs | 953 +++++++++ Monitor/MonitorDataStore.cs | 1698 +++++++++++++++++ Net/Emailer.cs | 177 ++ Net/IPHostHelper.cs | 832 ++++++++ Net/RouterHelper.cs | 14 + Net/WCHelper.cs | 73 + Net/WSCaller.cs | 292 +++ Other/CMDLine.cs | 423 ++++ Other/CMDcommands.cs | 102 + Other/CMDexecute.cs | 83 + Other/DelegateCollection.cs | 36 + Other/Installer.cs | 43 + Other/OSInfo.cs | 116 ++ Other/StackWalker.cs | 57 + Other/StateM.cs | 255 +++ Other/TraceM.cs | 78 + Other/Versioning.cs | 550 ++++++ Process/PStartInfo.cs | 110 ++ Process/PStarter.cs | 274 +++ Process/ProcessW.cs | 309 +++ Process/ServiceW.cs | 212 ++ Properties/AssemblyInfo.cs | 36 + Registry/RegKey.cs | 487 +++++ Structs/sbstring.cs | 35 + Thread/DispatcherThread.cs | 507 +++++ Thread/SingleThreadTimer.cs | 143 ++ Thread/TStarter.cs | 140 ++ Thread/TTimer.cs | 143 ++ Thread/TTimerDisp.cs | 162 ++ Tools/Convert.cs | 45 + Tools/EnumTool.cs | 143 ++ Tools/ObjTool.cs | 269 +++ Tools/PathNaming.cs | 259 +++ Tools/StringTool.cs | 165 ++ WPF/KeepHidden.cs | 319 ++++ WPF/Snippets.cs | 156 ++ WPF/WPFWindowManager.cs | 232 +++ Web/HTTP.cs | 59 + Web/WebClientWithTimeout.cs | 43 + Win32/Advapi32.cs | 27 + Win32/AtomMessenger.cs | 321 ++++ Win32/COM.cs | 116 ++ Win32/Convert.cs | 70 + Win32/Definitions.cs | 601 ++++++ Win32/Functions.cs | 388 ++++ Win32/Gdi32.cs | 85 + Win32/Kernel32.cs | 71 + Win32/Shell32.cs | 29 + Win32/Structures.cs | 166 ++ Win32/User32.cs | 187 ++ Win32/WSock32.cs | 79 + Win32/Wininet.cs | 19 + Win32/uxDwm.cs | 29 + WinForms/ControlClickHelper.cs | 185 ++ WinForms/MDIHelper.cs | 187 ++ WinForms/MsgBox.cs | 731 +++++++ WinForms/SysTray.cs | 408 ++++ Xml/XSerializer.cs | 195 ++ Yaulw.csproj | 151 ++ Yaulw.csproj.vspscc | 10 + license.txt | 13 + 115 files changed, 27392 insertions(+) create mode 100644 .gitignore create mode 100644 @integrate/AlternateDataStreamWrapper.cs create mode 100644 @integrate/App.xaml.cs create mode 100644 @integrate/AppState.cs create mode 100644 @integrate/CMDlineHandler.cs create mode 100644 @integrate/CheckActiveTwo.cs create mode 100644 @integrate/ClickOnceUpdater.cs create mode 100644 @integrate/Component.All/Common.cs create mode 100644 @integrate/Component.All/Common_MediLytec.cs create mode 100644 @integrate/Component.All/Component.All.csproj create mode 100644 @integrate/Component.All/ComponentConfig.cs create mode 100644 @integrate/Component.All/Component_Binary_Manager.cs create mode 100644 @integrate/Component.All/Component_Setup_Manager.cs create mode 100644 @integrate/Component.All/EmbeddedComponentConfig.xml create mode 100644 @integrate/Component.All/GenericInstaller.cs create mode 100644 @integrate/Component.All/IInstallComponent.cs create mode 100644 @integrate/Component.All/IManageComponents.cs create mode 100644 @integrate/Component.All/ISetup.cs create mode 100644 @integrate/Component.All/Properties/AssemblyInfo.cs create mode 100644 @integrate/HiddenMainWindow.xaml create mode 100644 @integrate/HiddenMainWindow.xaml.cs create mode 100644 @integrate/InstallComponent.cs create mode 100644 @integrate/InstallComponent2.cs create mode 100644 @integrate/InstallComponent4.cs create mode 100644 @integrate/Logging.cs create mode 100644 @integrate/MsgBox.cs create mode 100644 @integrate/README.txt create mode 100644 @integrate/ResxHelper.cs create mode 100644 @integrate/Settings.cs create mode 100644 @integrate/Snippets.txt create mode 100644 @integrate/SysTray.cs create mode 100644 @integrate/TTimer.cs create mode 100644 @integrate/Trinet.Core.IO.Ntfs/AlternateDataStreamInfo.cs create mode 100644 @integrate/Trinet.Core.IO.Ntfs/FileStreamAttributes.cs create mode 100644 @integrate/Trinet.Core.IO.Ntfs/FileStreamType.cs create mode 100644 @integrate/Trinet.Core.IO.Ntfs/FileSystem.cs create mode 100644 @integrate/Trinet.Core.IO.Ntfs/SafeHGlobalHandle.cs create mode 100644 @integrate/Trinet.Core.IO.Ntfs/SafeNativeMethods.cs create mode 100644 @integrate/Trinet.Core.IO.Ntfs/StreamName.cs create mode 100644 @integrate/WCFHost.cs create mode 100644 Assembly/AssemblyW.cs create mode 100644 Components/ICSharpCode.SharpZipLib.dll create mode 100644 Components/log4net.dll create mode 100644 Encryption/TextEncryptHash.cs create mode 100644 File/CHMFile.cs create mode 100644 File/FileWriter.cs create mode 100644 File/INIFile.cs create mode 100644 File/ISReadWrite.cs create mode 100644 File/LineReplacer.cs create mode 100644 File/LoggerQuick.cs create mode 100644 File/Logging.cs create mode 100644 File/Tail.cs create mode 100644 Installer/Common.cs create mode 100644 Installer/FileIcon.cs create mode 100644 Installer/ShellLink.cs create mode 100644 Monitor/MonitorDataStore.cs create mode 100644 Net/Emailer.cs create mode 100644 Net/IPHostHelper.cs create mode 100644 Net/RouterHelper.cs create mode 100644 Net/WCHelper.cs create mode 100644 Net/WSCaller.cs create mode 100644 Other/CMDLine.cs create mode 100644 Other/CMDcommands.cs create mode 100644 Other/CMDexecute.cs create mode 100644 Other/DelegateCollection.cs create mode 100644 Other/Installer.cs create mode 100644 Other/OSInfo.cs create mode 100644 Other/StackWalker.cs create mode 100644 Other/StateM.cs create mode 100644 Other/TraceM.cs create mode 100644 Other/Versioning.cs create mode 100644 Process/PStartInfo.cs create mode 100644 Process/PStarter.cs create mode 100644 Process/ProcessW.cs create mode 100644 Process/ServiceW.cs create mode 100644 Properties/AssemblyInfo.cs create mode 100644 Registry/RegKey.cs create mode 100644 Structs/sbstring.cs create mode 100644 Thread/DispatcherThread.cs create mode 100644 Thread/SingleThreadTimer.cs create mode 100644 Thread/TStarter.cs create mode 100644 Thread/TTimer.cs create mode 100644 Thread/TTimerDisp.cs create mode 100644 Tools/Convert.cs create mode 100644 Tools/EnumTool.cs create mode 100644 Tools/ObjTool.cs create mode 100644 Tools/PathNaming.cs create mode 100644 Tools/StringTool.cs create mode 100644 WPF/KeepHidden.cs create mode 100644 WPF/Snippets.cs create mode 100644 WPF/WPFWindowManager.cs create mode 100644 Web/HTTP.cs create mode 100644 Web/WebClientWithTimeout.cs create mode 100644 Win32/Advapi32.cs create mode 100644 Win32/AtomMessenger.cs create mode 100644 Win32/COM.cs create mode 100644 Win32/Convert.cs create mode 100644 Win32/Definitions.cs create mode 100644 Win32/Functions.cs create mode 100644 Win32/Gdi32.cs create mode 100644 Win32/Kernel32.cs create mode 100644 Win32/Shell32.cs create mode 100644 Win32/Structures.cs create mode 100644 Win32/User32.cs create mode 100644 Win32/WSock32.cs create mode 100644 Win32/Wininet.cs create mode 100644 Win32/uxDwm.cs create mode 100644 WinForms/ControlClickHelper.cs create mode 100644 WinForms/MDIHelper.cs create mode 100644 WinForms/MsgBox.cs create mode 100644 WinForms/SysTray.cs create mode 100644 Xml/XSerializer.cs create mode 100644 Yaulw.csproj create mode 100644 Yaulw.csproj.vspscc create mode 100644 license.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95471e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +!.gitignore + +# iOS Ignores +.DS_Store +*.swp +*~.nib + +build/ +Target/ + +*.pbxuser +*.perspective +*.perspectivev3 + +*.mode1v3 +*mode2v3 + +xcuserdata + +#MonoTouch Ignores +*.userprefs +bin/ +obj/ + +*/bin/* +*/obj/* +*/*/bin/* +*/*/obj/* +*/_ReSharper.*/* +*.user +*.suo +*.pidb +*.userprefs +#*.Designer.cs +#*.designer.cs +*.DS_Store +*.db diff --git a/@integrate/AlternateDataStreamWrapper.cs b/@integrate/AlternateDataStreamWrapper.cs new file mode 100644 index 0000000..830465f --- /dev/null +++ b/@integrate/AlternateDataStreamWrapper.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PhoneHome.Lib.Assembly; + +// Downloaded from +// http://www.codeproject.com/Articles/2670/Accessing-alternative-data-streams-of-files-on-an +using Trinet.Core.IO.Ntfs; +using System.IO; + +namespace PhoneHome +{ + /// + /// Wrapper class arround Alternate Data Streams + /// * IMP * Registrate 'Later' Medisoft Demo Registration Work-arround + /// Medisoft has a 30 day trial where they don't enter a serial number. However, this can + /// be bypassed, since registration is terrible at what it does. So we must make sure that + /// the 30 days haven't passed, and we deal with it here with PhoneHome via AlternateStreams + /// + internal static class AlternateDataStreamWrapper + { + /// + /// Read the Timestamp found in the Alternative Stream + /// + /// Dt found or DT.Min (if none found) + internal static DateTime ReadDateTimeStamp() + { + try + { + string s_curDir = Path.GetDirectoryName(AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Executing)); + SafeNativeMethods.Win32StreamInfo s_streamInfo = new SafeNativeMethods.Win32StreamInfo() { StreamAttributes = FileStreamAttributes.None, StreamName = "phdt", StreamSize = 20, StreamType = FileStreamType.Data }; + + AlternateDataStreamInfo adataStream = new AlternateDataStreamInfo(s_curDir, s_streamInfo); + using (FileStream fs = adataStream.OpenRead()) + using (StreamReader sr = new StreamReader(fs)) + { + string strLine = sr.ReadLine(); + DateTime dtFound = DateTime.Parse(strLine); + return dtFound; + } + } + catch (Exception e) { string Message = e.Message; } + return DateTime.MinValue; + } + + /// + /// Write the passed in Timestamp to the Alternative Stream + /// + /// Timestamp to write + internal static void WriteDateTimeStamp(DateTime dtStamp) + { + try + { + string s_curDir = Path.GetDirectoryName(AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Executing)); + SafeNativeMethods.Win32StreamInfo s_streamInfo = new SafeNativeMethods.Win32StreamInfo() { StreamAttributes = FileStreamAttributes.None, StreamName = "phdt", StreamSize = 20, StreamType = FileStreamType.Data }; + + AlternateDataStreamInfo adataStream = new AlternateDataStreamInfo(s_curDir, s_streamInfo); + using (FileStream fs = adataStream.OpenWrite()) + using (StreamWriter sw = new StreamWriter(fs)) + { + sw.WriteLine(dtStamp.ToShortDateString()); + } + } + catch (Exception e) { string Message = e.Message; } + } + + } + +} diff --git a/@integrate/App.xaml.cs b/@integrate/App.xaml.cs new file mode 100644 index 0000000..4f2eb5a --- /dev/null +++ b/@integrate/App.xaml.cs @@ -0,0 +1,839 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Windows; +using Watchdog.WatchdogLib.File; +using WatchdogLib.Tools; +using Watchdog.WatchdogLib.Process; +using Watchdog.WatchdogLib.WinForms; +using Watchdog.WatchdogLib.Assembly; +using WatchdogLib.File; +using System.Reflection; +using Watchdog.WatchdogLib.Other; +using Watchdog.WatchdogLib.Monitor; +using System.Diagnostics; +using Forms = System.Windows.Forms; +using Watchdog.WatchdogLib.Net; +using System.IO; +using System.Net; + +namespace Watchdog +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + /// + /// Main Application Object + /// + public App() + { + AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); + AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); + } + + #region Application Constants + + // Application Constants + internal static readonly string APPLICATION_NAME_SHORT = AppResx.GetString("APPLICATION_NAME_SHORT"); + internal static readonly string APPLICATION_NAME_LONG = AppResx.GetString("APPLICATION_NAME_LONG"); + internal static readonly string SUPPORT_PHONENUMBER = AppResx.GetString("SUPPORT_PHONENUMBER"); + internal static readonly int APPLICATION_VERSION_MAJOR = AssemblyW.GetAssemblyVersion(AssemblyW.AssemblyST.Executing).Major; + internal static readonly int APPLICATION_VERSION_MINOR = AssemblyW.GetAssemblyVersion(AssemblyW.AssemblyST.Executing).Minor; + internal static readonly int APPLICATION_VERSION_BUILDNUMBER = AssemblyW.GetAssemblyVersion(AssemblyW.AssemblyST.Executing).Build; + internal static readonly int APPLICATION_VERSION_REVISION = AssemblyW.GetAssemblyVersion(AssemblyW.AssemblyST.Executing).Revision; + internal static string APPLICATION_VERSION { get { return (APPLICATION_VERSION_MAJOR.ToString() + "." + APPLICATION_VERSION_MINOR.ToString() + "." + APPLICATION_VERSION_BUILDNUMBER.ToString() + "." + APPLICATION_VERSION_REVISION.ToString()); } } + + /// + /// This is the Product ID Generated for our Product via ClickOnce and our Certificate. + /// As long as the Certificate Stays the same This ID stays the same. + /// ~we are using this to overide the uninstall icon in Add/Remove * Branding * + /// + internal static readonly string APPLICATION_PRODUCT_CLICKONCE_ID = "6f02138d8632343a"; + internal static readonly string APPLICATION_PRODUCT_CLICKONCE_URL = "http://www.medisoft.com/Watchdog/Publish/Watchdog.application"; + internal static string APPLICATION_CLICKONCE_PUBLISHER { get { return AssemblyW.GetAssemblyCompany(AssemblyW.AssemblyST.Executing); } } + internal static string APPLICATION_CLICKONCE_PRODUCT { get { return AssemblyW.GetAssemblyProductName(AssemblyW.AssemblyST.Executing); } } + internal static string APPLICATION_CLICKONCE_STARTMENU_LINK + { + get { return string.Concat(Environment.GetFolderPath(Environment.SpecialFolder.Programs), "\\", APPLICATION_CLICKONCE_PUBLISHER, "\\", APPLICATION_CLICKONCE_PRODUCT , ".appref-ms"); } + } + + // Default Log File Settings + internal const int LOG_FILE_FILE_SIZE_IN_MB = 2; + internal const int LOG_FILE_NUM_OF_BACKUPS = 4; + + // Log File Constants + internal static readonly string FILE_EXTENSION_LOG_DEFAULT = AppResx.GetString("FILE_EXTENSION_LOG_DEFAULT"); + internal static readonly string LOG_NAME_APPMAIN = AppResx.GetString("APPLICATION_NAME_SHORT"); + + // Application Files / Dependencies + internal static string APP_LOG_FILENAME { get { return (LOG_NAME_APPMAIN + "." + FILE_EXTENSION_LOG_DEFAULT); } } + internal static string APP_LOG_FILENAMEANDPATH + { + get + { + // Make sure Subpath begin|end with a slash, as needed + string commondir = PathNaming.PathEndsWithSlash(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)); + string subpath = PathNaming.PathBeginsWithNoSlash(PathNaming.PathEndsWithSlash(AppResx.GetString("LOG_FILE_SUBPATH"))); + string filename = LOG_NAME_APPMAIN + "." + FILE_EXTENSION_LOG_DEFAULT; + return (commondir + subpath + filename); + } + } + internal static string APP_LOG_PATH + { + get + { + // Make sure Subpath begin|end with a slash, as needed + string commondir = PathNaming.PathEndsWithSlash(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)); + string subpath = PathNaming.PathBeginsWithNoSlash(PathNaming.PathEndsWithNoSlash(AppResx.GetString("LOG_FILE_SUBPATH"))); + return (commondir + subpath); + } + } + internal static string APP_XML_DS_FILENAME { get { return AppResx.GetString("XMLCONFIG_FILENAME"); } } + internal static string APP_XML_DS_FILENAMEANDPATH + { + get + { + // Make sure Subpath begin|end with a slash, as needed + string commondir = PathNaming.PathEndsWithSlash(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)); + string subpath = PathNaming.PathBeginsWithNoSlash(PathNaming.PathEndsWithSlash(AppResx.GetString("XMLCONFIG_FILE_SUBPATH"))); + string filename = AppResx.GetString("XMLCONFIG_FILENAME"); + return (commondir + subpath + filename); + } + } + internal static string APP_XML_DS_PATH + { + get + { + // Make sure Subpath begin|end with a slash, as needed + string commondir = PathNaming.PathEndsWithSlash(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)); + string subpath = PathNaming.PathBeginsWithNoSlash(PathNaming.PathEndsWithNoSlash(AppResx.GetString("XMLCONFIG_FILE_SUBPATH"))); + return (commondir + subpath); + } + } + internal static string APP_INI_SETTINGS_FILENAME { get { return AppResx.GetString("INISETTINGS_FILENAME"); } } + internal static string APP_INI_SETTINGS_FILENAMEANDPATH + { + get + { + // Make sure Subpath begin|end with a slash, as needed + string commondir = PathNaming.PathEndsWithSlash(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)); + string subpath = PathNaming.PathBeginsWithNoSlash(PathNaming.PathEndsWithSlash(AppResx.GetString("INISETTINGS_SUBPATH"))); + string filename = AppResx.GetString("INISETTINGS_FILENAME"); + return (commondir + subpath + filename); + } + } + internal static string APP_INI_SETTINGS_PATH + { + get + { + // Make sure Subpath begin|end with a slash, as needed + string commondir = PathNaming.PathEndsWithSlash(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)); + string subpath = PathNaming.PathBeginsWithNoSlash(PathNaming.PathEndsWithNoSlash(AppResx.GetString("INISETTINGS_SUBPATH"))); + return (commondir + subpath); + } + } + + #endregion + + #region Application Help + + // Application's Main Help Object + internal static CHMFile chmhelp = null; + + #endregion + + #region Xml Configuration + + // Application's Main Xml Object + internal static MonitorDataStore xmlfile = null; + + #endregion + + #region Ini Configuration + + // Application's Main Ini Object + internal static INIFile inifile = new INIFile(APP_INI_SETTINGS_FILENAMEANDPATH, typeof(Ini_Setting)); + + internal enum Ini_Setting + { + MonitorSettings__Start_With_Windows, + MonitorSettings__Monitor_On_Start, + MonitorSettings__Max_Fail_Count, + MonitorSettings__Max_Fail_Count_Per_Hour, + MonitorSettings__Default_Exe_Running_Filter, + MonitorSettings__Default_Services_Names_Running_Filter, + MonitorSettings__Sheduler_StartDateTime, + MonitorSettings__Sheduler_RepeatEveryNHoursOrMinutes, + MonitorSettings__Sheduler_RepeatIsHour, + MonitorSettings__Sheduler_LastRun_DateTime, + MonitorSettings__Scheduler_Restarts_Services, + MonitorSettings__ShedulerBackup_StartDateTime, + MonitorSettings__ShedulerBackup_ForDurationNHoursOrMinutes, + MonitorSettings__ShedulerBackup_DurationIsHour, + MonitorSettings__ShedulerBackup_LastRun_DateTime, + MonitorSettings__ShedulerBackup_StopInsteadOfPause, + MonitorSettings__ShedulerBackup_StopServices, + WindowSettings__AboutWindow_TopLeft, + WindowSettings__SettingsWindow_TopLeft, + WindowSettings__LogViewerWindow_TopLeft, + WindowsSettings__LockWorkstation_With_Windows, + WindowsSettings__AutoLogin_With_Windows, + WindowsSettings__CreateExeHardLink_In_RootPath, + EmailSettings__EnableEmailNotifications, + EmailSettings__SmtpServer, + EmailSettings__SenderEmail, + EmailSettings__ReveiverEmail, + EmailSettings__SmtpPort, + EmailSettings__SmtpRequiresAuth, + EmailSettings__SmtpRequiresSSL, + EmailSettings__SmtpUsername, + EmailSettings__SmtpPassword, + EmailSettings__Custom_Message_Text, + } + + // Wrapper class arround Ini Settings + internal static Settings setting = new Settings(); + + #endregion + + #region Command Line Parameters + + // Application's CmdLine Parser + internal static CMDline cmdline = new CMDline(typeof(App.CommandLine_Option), typeof(App.CommandLine_Flag)); + + /// + /// CommandLine Flags for Application + /// + public enum CommandLine_Flag + { + START, + PAUSE, + RESTART, + RESTART_ALL, + STOP, + STOP_ALL, + SHOW_PROCESSES, + SHOW_SERVICES, + LOCK, + RUNASSERVICE, + UNINSTALL, + } + + /// + /// CommandLine Options for Application + /// + public enum CommandLine_Option + { + ADD_SERVICE, + REMOVE_SERVICE, + ADD_PROCESS, + REMOVE_PROCESS, + } + + #endregion + + #region Logging Configuration + + // Application's Main Log Object + internal static Logging log = null; + + /// + /// Create a Logging_Configuration Object with Default Application Settings + /// + /// Specify the LogFile and Path to Log to + /// Specify Logging Details Setting + /// true, to only allow exclusive (one process) access to file + /// a Logging_Configuration object to use Utilities.GenericUtilities.File.Logging + //private static Logging_Configuration CreateDefaultLoggingConfiguration(string LogFileNameNPath, Logging_Detail Detail = Logging_Detail.ERROR, bool UseExclusiveFileLock = false) + private static Logging_Configuration CreateDefaultLoggingConfiguration(string LogFileNameNPath, Logging_Detail Detail, bool UseExclusiveFileLock) + { + Logging_Configuration config; + config.LogFileNameNPath = LogFileNameNPath; + config.maxFileSizeInMB = LOG_FILE_FILE_SIZE_IN_MB; + config.numOfBackupLogFiles = LOG_FILE_NUM_OF_BACKUPS; + config.Detail = Detail; + config.UseExclusiveFileLock = UseExclusiveFileLock; + return config; + } + + /// + /// Responsible for creating the AppMain Logging Object + /// + public static Logging CreateAppMainLogging() + { + // Log FileNameNPath * Debug mode set logging level to Debug, Release to Info * +#if DEBUG + return Logging.AddGlobalLoggerConfiguration(LOG_NAME_APPMAIN, CreateDefaultLoggingConfiguration(APP_LOG_FILENAMEANDPATH, Logging_Detail.DEBUG, false)); +#else + return Logging.AddGlobalLoggerConfiguration(LOG_NAME_APPMAIN, CreateDefaultLoggingConfiguration(APP_LOG_FILENAMEANDPATH, Logging_Detail.INFO, false)); +#endif + } + + /// + /// Retrieve the AppMain Logging Object, It must have been created by calling CreateAppMainLogging() + /// + /// + public static Logging GetAppMainLogging() + { + return Logging.GetLogger(LOG_NAME_APPMAIN); + } + + #endregion + + #region Message Box Configuration + + /// + /// We also want to initialize the MsgBox Class here + /// + public static void ConfigureAppMainMsgBox() + { + // Fatal Errors + MsgBox.MsgBox_FatalErrorTitleHeader = AppResx.GetStringFormated("APPLICATION_FATAL_ERROR", APPLICATION_NAME_SHORT); + MsgBox.MsgBox_FatalErrorHeader = AppResx.GetString("FATAL_ERROR_HEADER"); + MsgBox.MsgBox_FatalErrorFooter = AppResx.GetStringFormated("FATAL_ERROR_APPLICATION_WILL_EXIT", APPLICATION_NAME_LONG) + + AppResx.GetStringFormated("CONTACT_SUPPORT_URGENTLY", SUPPORT_PHONENUMBER) + + AppResx.GetStringFormated("ERROR_LOG_FILE_LOCATION", APP_LOG_FILENAME, APP_LOG_PATH); + + // Errors + MsgBox.MsgBox_ErrorTitleHeader = AppResx.GetStringFormated("APPLICATION_ERROR", APPLICATION_NAME_SHORT); + MsgBox.MsgBox_ErrorHeader = AppResx.GetString("ERROR_HEADER"); + MsgBox.MsgBox_ErrorFooter = AppResx.GetStringFormated("CONTACT_SUPPORT_NICELY", SUPPORT_PHONENUMBER) + + AppResx.GetStringFormated("ERROR_LOG_FILE_LOCATION", APP_LOG_FILENAME, APP_LOG_PATH); + + // Info + MsgBox.MsgBox_InfoTitleHeader = AppResx.GetStringFormated("APPLICATION_INFO", APPLICATION_NAME_SHORT); + + // * For Debugging * + //MsgBox.ShowError("01234567890123456789012345678901234567890123456789012345678901234"); + //MsgBox.ShowFatalError("01234567890123456789012345678901234567890123456789012345678901234"); + } + + #endregion + + #region Application State + + /// + /// Various Keys that we can use to save/get the GUI State + /// + internal enum StateKey + { + Monitor_Started_bool, + Monitor_ErrorsOccured_bool, + Scheduler_Started_bool, + SchedulerBackup_Started_bool, + SpecialMode_Paused_Mode_bool, + SpecialMode_CommandLine_Mode_bool, + SpecialMode_RunAsService_Mode_bool, + SpecialCircum_CommandLine_ConsoleWindowIsAttached_bool, + SpecialCircum_CommandLine_MonitorInstanceExists_bool, + SpecialCircum_DontCloseApplicationsOnExit_bool, + Help_Is_Available_bool, + } + + /// + /// State of the System + /// + public enum StateSystem + { + Running, + Paused, + Stopped, + Error + } + + // Initialize BkgdState + internal static readonly StateM State = new StateM(typeof(StateKey)); + + #endregion + + #region Application State - Monitor State + + /// + /// Returns the state of the System (icon to display) + /// + /// Application State for System + internal static StateSystem State_GetSystemState() + { + if (State.GetStateValue(App.StateKey.Monitor_Started_bool, false)) + { + // Error and Pause are Special 'Running' States + if (State.GetStateValue(App.StateKey.SpecialMode_Paused_Mode_bool, false)) + return StateSystem.Paused; + if (State.GetStateValue(App.StateKey.Monitor_ErrorsOccured_bool, false)) + return StateSystem.Error; + else + return StateSystem.Running; + } + else + { + return StateSystem.Stopped; + } + } + + /// + /// Is the Monitor Currently in a 'Running' State + /// + /// true if yes, false otherwise + internal static bool State_MonitorIsInRunningState() { return State.GetStateValue(App.StateKey.Monitor_Started_bool, false); } + + /// + /// Sets the System into an Error State + /// + /// Error Message to Notify to User + internal static void State_Error_SetErrorState(string strErrorMessageToNotify) + { + App.State.SetStateValue(App.StateKey.Monitor_ErrorsOccured_bool, true); + if (!String.IsNullOrEmpty(strErrorMessageToNotify)) + { + //// + // Application entered an Error State * Better Notify User, if specified * + //// + if (App.setting.EmailNotificationEnabled && App.setting.EmailSettingsValid) + { + bool bIsSend = Emailer.SendEmail(App.setting.EmailStmpServer, App.setting.EmailSenderEmail, App.APPLICATION_NAME_SHORT, + App.setting.EmailReceiverEmail, AppResx.GetStringFormated("ERRORSTATE_EMAIL_SUBJECT", App.APPLICATION_NAME_SHORT), + AppResx.GetStringFormated("ERRORSTATE_EMAIL_BODY", App.APPLICATION_NAME_SHORT, strErrorMessageToNotify ,App.setting.EmailCustomMessageText), + App.setting.EmailSmtpPort, String.Empty, App.setting.EmailStmpServerRequiresAuth, App.setting.EmailSmtpUsername, App.setting.EmailSmtpPassword, + App.setting.EmailStmpServerRequiresSSL, 30); + if (!bIsSend) + App.log.Error("Failed to Send ERRORSTATE_EMAIL"); + else + App.log.Info(String.Format("Email (ERRORSTATE_EMAIL) Notification send to {0}", App.setting.EmailReceiverEmail)); + } + } + } + + /// + /// Error State Reset + /// + internal static void State_Error_ResetErrorState() { App.State.SetStateValue(App.StateKey.Monitor_ErrorsOccured_bool, false); } + + /// + /// Set Main Monitor to Started + /// + internal static void State_MonitorStarted() { App.State.SetStateValue(App.StateKey.Monitor_Started_bool, true); App.State.SetStateValue(App.StateKey.SpecialMode_Paused_Mode_bool, false); } + + /// + /// Set Main Monitor to Stopped + /// + internal static void State_MonitorStopped() { App.State.SetStateValue(App.StateKey.Monitor_Started_bool, false); App.State.SetStateValue(App.StateKey.SpecialMode_Paused_Mode_bool, false); } + + /// + /// Set Main Monitor to Paused + /// + internal static void State_MonitorPaused() { App.State.SetStateValue(App.StateKey.SpecialMode_Paused_Mode_bool, true); } + + /// + /// Is the System in a Paused State? + /// + /// true if yes, false if no + internal static bool State_IsMonitorPaused() { return App.State.GetStateValue(App.StateKey.SpecialMode_Paused_Mode_bool, false); } + + #endregion + + #region Application State - Scheduler State + + /// + /// Is the Scheduler Executing? + /// + /// true if yes, false if no + internal static bool State_IsSchedulerExecuting() { return App.State.GetStateValue(App.StateKey.Scheduler_Started_bool, false); } + + /// + /// Scheduler Execution Started + /// + internal static void State_SchedulerExecution_Started() { App.State.SetStateValue(App.StateKey.Scheduler_Started_bool, true); } + + /// + /// Scheduler Execution Stopped + /// + internal static void State_SchedulerExecution_Stopped() { App.State.SetStateValue(App.StateKey.Scheduler_Started_bool, false); } + + /// + /// Is the Scheduler Executing? + /// + /// true if yes, false if no + internal static bool State_IsSchedulerBackupExecuting() { return App.State.GetStateValue(App.StateKey.SchedulerBackup_Started_bool, false); } + + /// + /// Scheduler Execution Started + /// + internal static void State_SchedulerBackupExecution_Started() { App.State.SetStateValue(App.StateKey.SchedulerBackup_Started_bool, true); } + + /// + /// Scheduler Execution Stopped + /// + internal static void State_SchedulerBackupExecution_Stopped() { App.State.SetStateValue(App.StateKey.SchedulerBackup_Started_bool, false); } + + #endregion + + #region Application State - CommandLine State + + /// + /// Set Application into CommandLine Mode + /// + internal static void State_SpecialMode_CommandLineSet() { App.State.SetStateValue(App.StateKey.SpecialMode_CommandLine_Mode_bool, true); } + + /// + /// Is Application in CommandLine Mode? + /// + /// true if yes, false if no + internal static bool State_SpecialMode_IsCommandLineSet() { return App.State.GetStateValue(App.StateKey.SpecialMode_CommandLine_Mode_bool, false); } + + /// + /// Set Application into RunAsService Mode + /// + internal static void State_SpecialMode_RunAsServiceSet() { App.State.SetStateValue(App.StateKey.SpecialMode_RunAsService_Mode_bool, true); } + + /// + /// Is Application in RunAsService Mode? + /// + /// true if yes, false if no + internal static bool State_SpecialMode_IsRunAsServiceSet() { return App.State.GetStateValue(App.StateKey.SpecialMode_RunAsService_Mode_bool, false); } + + /// + /// Special Circumstance - Set Console as Attached + /// + internal static void State_SpecialCircum_ConsoleWindowIsAttached() { App.State.SetStateValue(App.StateKey.SpecialCircum_CommandLine_ConsoleWindowIsAttached_bool, true); } + + /// + /// Special Circumstance - Was Console Window Attached? + /// + internal static bool State_SpecialCircum_IsConsoleWindowIsAttached() { return App.State.GetStateValue(App.StateKey.SpecialCircum_CommandLine_ConsoleWindowIsAttached_bool, false); } + + /// + /// Special Circumstance - Set Communication with Main Instance via WCF as Succeeded + /// + internal static void State_SpecialCircum_MainMonitorInstance_CommSuccees() { App.State.SetStateValue(App.StateKey.SpecialCircum_CommandLine_MonitorInstanceExists_bool, true); } + + /// + /// Special Circumstance - Did Communication with Mai Instance via WCF Succeed? + /// + /// true if yes, false if no + internal static bool State_SpecialCircum_DidMainMonitorInstanceCommSucceed() { return App.State.GetStateValue(App.StateKey.SpecialCircum_CommandLine_MonitorInstanceExists_bool, false); } + + #endregion + + #region Applicaton State - Special States + + /// + /// Special Circumstance - Set that the Application won't close Applications on Exit * Useful for Auto-Updating the Software * + /// + internal static void State_SpecialCircum_DontCloseApplicationsOnExit() { App.State.SetStateValue(App.StateKey.SpecialCircum_DontCloseApplicationsOnExit_bool, true); } + + /// + /// Special Circumstance - Check to see if we should Close Applications when Exiting + /// + /// true if yes, don't close, false if no + internal static bool State_SpecialCircum_ShouldWeNotCloseApplicationsOnExit() { return App.State.GetStateValue(App.StateKey.SpecialCircum_DontCloseApplicationsOnExit_bool, false); } + + /// + /// Check to see if html is available + /// + /// true, if available, false otherwise + internal static bool State_HtmlHelpIsAvailable() { return App.State.GetStateValue(App.StateKey.Help_Is_Available_bool, false); } + + /// + /// Set Html Help File as available + /// + internal static void State_HtmlHelpAvailable() { App.State.SetStateValue(App.StateKey.Help_Is_Available_bool, true); } + + #endregion + + #region Unhandled Expections! IMP - Show WinForm and Log + + /// + /// * Generic Unhandled Exception Handler * + /// Handles all unhandled Exceptions for the Entire AppDomain. + /// First Show a Window Message Box, so that we can for sure capture the message + /// Second Log it + /// + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + Exception ex = (Exception)e.ExceptionObject; + + // Exeption to capture error information + string exceptionMessage = AppResx.GetString("FATAL_ERROR_HEADER"); + exceptionMessage = AppResx.GetStringFormated("FATAL_ERROR_APPLICATION_WILL_EXIT", APPLICATION_NAME_LONG); + exceptionMessage += ex.Message + "\n\n"; + if (!String.IsNullOrEmpty(ex.StackTrace)) + exceptionMessage += ex.StackTrace.Substring(0, 880) + "\n\n"; + if(!String.IsNullOrEmpty(ex.InnerException.Message)) + exceptionMessage += ex.InnerException.Message + "\n\n"; + if (!String.IsNullOrEmpty(ex.Source)) + exceptionMessage += ex.Source + "\n\n"; + + // Polite Message to Show in Message Box + string PoliteExceptionMessage = exceptionMessage + + AppResx.GetStringFormated("CONTACT_SUPPORT_URGENTLY", SUPPORT_PHONENUMBER) + + AppResx.GetStringFormated("ERROR_LOG_FILE_LOCATION", APP_LOG_FILENAME, APP_LOG_PATH); + + // Show Message Box First - Guaranteed to work (Polite Exception Message) + MessageBox.Show(PoliteExceptionMessage, AppResx.GetStringFormated("APPLICATION_FATAL_ERROR", APPLICATION_NAME_SHORT), MessageBoxButton.OK, MessageBoxImage.Error); + + // Log the Error to the Main Log File + CreateAppMainLogging().Fatal(exceptionMessage, ex); + } + + #endregion + + #region Fatal Exception! IMP - Show WinForm, Log, and Exit the Application + + /// + /// Some Events continue execution, even though a fatal exception occured (.net!) + /// This flag allows those functions to check for this and stop processing + /// + public static bool s_FatalErrorOccured = false; + + /// + /// Custom User Specified Fatal Exception Occured * Stops Application Execution * + /// + /// Message to show/log + //public static void FatalExceptionStopExecution(string Message, bool bShowMessageBox = true) + public static void FatalExceptionStopExecution(string Message, bool bShowMessageBox) + { + s_FatalErrorOccured = true; + log.Fatal(Message); + if(bShowMessageBox && !App.State_SpecialMode_IsRunAsServiceSet()) + MsgBox.ShowFatalError(Message, "", Forms.MessageBoxButtons.OK); + App.Current.Shutdown(); + + // To make 100% sure, that we are exiting... (not needed) + //System.Diagnostics.Process.GetCurrentProcess().Kill(); + } + + #endregion + + #region Application Multi-File Assembly Handling + + /// + /// A way to embed multiple dlls into one exe: + /// http://blogs.msdn.com/b/microsoft_press/archive/2010/02/03/jeffrey-richter-excerpt-2-from-clr-via-c-third-edition.aspx + /// + /// a loaded assembly if found, null otherwise + System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + string curAssemblyName = AssemblyW.GetAssemblyName(AssemblyW.AssemblyST.Executing); + String resourceName = curAssemblyName + ".Components." + new AssemblyName(args.Name).Name + ".dll"; + + //string[] resources = AssemblyW.SpecializedAssemblyInfo.GetAssemblyResourceNames(AssemblyW.AssemblyST.Entry); + var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); + if(stream != null) + { + using (stream) + { + Byte[] assemblyData = new Byte[stream.Length]; + stream.Read(assemblyData, 0, assemblyData.Length); + return Assembly.Load(assemblyData); + } + } + return null; + } + + #endregion + + #region Application Startup N' Exit + + /// + /// Handles the Application StartUp event + /// + private void Application_Startup(object sender, StartupEventArgs e) + { + // We need to make sure that the Permissions are set correctly for + // the ProgramData/AllUser folder for ALL our configuration files + Installer.GrantFullPermissionToFolderForUserOrGroup(APP_XML_DS_PATH, "Everyone"); + + // Create the First Main Log Instance + log = CreateAppMainLogging(); + + // Parse the Command Line + cmdline.Parse(e.Args); + + // Delay Start * Imp. for Auto-Update Feature + string[] activationData = null; + if(AppDomain.CurrentDomain.SetupInformation.ActivationArguments != null) + activationData = AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData; + if (activationData != null && activationData.Length > 0) + { + uint uDelayStart = 0; + if (uint.TryParse(activationData[0], out uDelayStart) && (uDelayStart > 0)) + { + App.log.Info(String.Format("Auto Update Delay Start Called with {0} Seconds", uDelayStart)); + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(uDelayStart)); + } + } + + // If Command-Line has params, we are being launched in Command-Line Mode * Hence, we put + // the app into a different State * + if (cmdline.HasParams && !cmdline.GetFlagValue(CommandLine_Flag.RUNASSERVICE)) // RunAsService is NOT Command-line Mode + { + App.State_SpecialMode_CommandLineSet(); + log.Info(AppResx.GetStringFormated("APPLICATION_STARTED_COMMANDLINE_MODE", APPLICATION_NAME_SHORT, cmdline.ParsedArgs(), APPLICATION_VERSION)); + } + else if (cmdline.HasParams && cmdline.GetFlagValue(CommandLine_Flag.RUNASSERVICE)) // RunAsService is it's own Mode + { + State_SpecialMode_RunAsServiceSet(); + log.Info(AppResx.GetStringFormated("APPLICATION_STARTED_SERVICE_MODE", APPLICATION_NAME_SHORT, cmdline.ParsedArgs(), APPLICATION_VERSION)); + } + else + { + log.Info(AppResx.GetStringFormated("APPLICATION_STARTED", APPLICATION_NAME_SHORT, APPLICATION_VERSION)); + } + + // Configure our Message Boxes + ConfigureAppMainMsgBox(); + + // Create the Xml File Instance * To Read/Write DataStore * + // ~Also, don't allow the configuration to add this Application to the Configuration + DelegateCollection.Void_Param1_Exception_Func XmlFileExceptionHandler = delegate(Exception ex) + { + App.log.Error("Saving MonitorDataStore XMLFile Error Thrown", ex); + if(!App.State_SpecialMode_IsRunAsServiceSet()) + MsgBox.ShowError("Saving XMLDataStore XMLFile Error " + ex.Message, "", Forms.MessageBoxButtons.OK); + }; + List excludedProcessNames = new List(); + excludedProcessNames.Add(AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileName(AssemblyW.AssemblyST.Entry)); + xmlfile = new MonitorDataStore(App.APP_XML_DS_FILENAMEANDPATH, excludedProcessNames, XmlFileExceptionHandler); + + // If this is a Debug build, enable * Performance Tracing * + #if DEBUG + TraceM.EnableTracing = true; + #endif + + // Make sure that the File Name of this Assembly matches the Assembly Name, + // this allows us to enforce for sure that only One Instance of this program is running + // i.e. someone could run this program otherwise by just changing the filename + string entryAssemblyName = AssemblyW.GetAssemblyName(AssemblyW.AssemblyST.Entry); + string entryAssemblyFileName = AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileName(AssemblyW.AssemblyST.Entry); + if (String.Compare(entryAssemblyName, entryAssemblyFileName, true) != 0) + FatalExceptionStopExecution(AppResx.GetStringFormated("FATAL_ERROR_ASSEMBLY_FILENAME_MISMATCH", entryAssemblyName, entryAssemblyFileName), true); + + // Check that this is the ONLY Instance running on this Machine + // ~We only allow one instance of this app to be running... (Only Do this if NOT in Command-Line Mode) + if (!s_FatalErrorOccured && !App.State_SpecialMode_IsCommandLineSet()) + { + // Check if there are other instances. Start/Stop/Etc won't work if other instances are not running + bool bAnotherInstanceIsRunning = true; +#if DEBUG + bAnotherInstanceIsRunning = !ProcessW.IsTheOnlyProcessRunning(AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileName(AssemblyW.AssemblyST.Entry).ToLower(), false); +#else + bAnotherInstanceIsRunning = !ProcessW.IsTheOnlyProcessRunning(AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileName(AssemblyW.AssemblyST.Entry).ToLower(), true); +#endif + if (bAnotherInstanceIsRunning) + { + // Alert the User and ask the User, if they would like to close that process, + // * Could be that The Application Errored out, yet still remains running, so they are trying to restart it * + Process[] ps = null; +#if DEBUG + ps = ProcessW.AllRunningProcessesOf(AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileName(AssemblyW.AssemblyST.Entry).ToLower(), false, true); +#else + ps = ProcessW.AllRunningProcessesOf(AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileName(AssemblyW.AssemblyST.Entry).ToLower(), true, true); +#endif + Forms.DialogResult dr = System.Windows.Forms.DialogResult.Ignore; + if (!App.State_SpecialMode_IsRunAsServiceSet()) + { + string msg = AppResx.GetStringFormated("FATAL_ERROR_OTHER_INSTANCES", App.APPLICATION_NAME_SHORT); + msg += "\nWould you like to try to force close the other instance(s)?\n"; + msg += "\nClicking 'Yes' will force all other instances to close."; + msg += "\nClicking 'No' will close this instance."; + dr = MsgBox.ShowInfo(msg, "Other Instance Found", Forms.MessageBoxButtons.YesNo); + } + + // Check Dialog Result (If in GUI MODE) + if (dr == Forms.DialogResult.Yes) + { + // try to kill all other instances + bool bSuccess = true; + foreach (Process p in ps) + { + if(Process.GetCurrentProcess().Id != p.Id) + bSuccess = PStarter.KillProcess((uint)p.Id, false, 1, true, 2); + if (!bSuccess) + break; + } + if (!bSuccess) + { + App.log.Error("Error occured closing other instances"); + FatalExceptionStopExecution(AppResx.GetStringFormated("FATAL_ERROR_OTHER_INSTANCES", App.APPLICATION_NAME_SHORT), false); + } + else + { + // * Opportunity To Start the WCF Host * + WCFHost.StartHost(); + + // Refresh the taskbar, after killing any instances + Watchdog.WatchdogLib.Win32.Functions.RefreshTaskbarNotificationArea(); + } + } + else + { + FatalExceptionStopExecution(AppResx.GetStringFormated("FATAL_ERROR_OTHER_INSTANCES", App.APPLICATION_NAME_SHORT), false); + } + } + else + { + // * Opportunity To Start the WCF Host * + WCFHost.StartHost(); + + // * Opportunity to load the chm help file * + // ~Force a new chm file write, if an upgrade occured + try + { + #if DEBUG + bool bForceCreationOfNewCHMFile = true; + #else + bool bForceCreationOfNewCHMFile = (App.APPLICATION_VERSION != App.setting.LastProgramVersion); + #endif + string curAssemblyName = AssemblyW.GetAssemblyName(AssemblyW.AssemblyST.Executing); + App.chmhelp = new CHMFile(Assembly.GetExecutingAssembly().GetManifestResourceStream(curAssemblyName + "." + "Watchdog.chm"), App.APPLICATION_NAME_SHORT, bForceCreationOfNewCHMFile); + if (App.chmhelp != null) + App.State_HtmlHelpAvailable(); // Html Help loaded successfully + if (bForceCreationOfNewCHMFile && (App.chmhelp != null)) + App.setting.LastProgramVersion = App.APPLICATION_VERSION; + } + catch (Exception ex) + { + App.log.Error("Failed to create chm Helf File", ex); + } + } + } + else if (!s_FatalErrorOccured && App.State_SpecialMode_IsCommandLineSet()) + { + // We are called with CmdLine Parameters * Try Attaching to the console, in case we got called from a Command Window * + bool bAttachSuccess = Watchdog.WatchdogLib.Win32.Kernel32.AttachConsole(-1); + App.log.Info(String.Format("CommandLineMode - Attached to Console is {0}", bAttachSuccess)); + if (bAttachSuccess) + App.State_SpecialCircum_ConsoleWindowIsAttached(); + } + } + + /// + /// Handles the Application Exit event. + /// + private void Application_Exit(object sender, ExitEventArgs e) + { + if (!App.State_SpecialMode_IsCommandLineSet()) + { + log.Info(AppResx.GetStringFormated("APPLICATION_ENDED", APPLICATION_NAME_SHORT, APPLICATION_VERSION)); + + // * Opportunity To Stop the WCF Host * + WCFHost.StopHost(); + } + else if(App.State_SpecialMode_IsCommandLineSet()) + { + if(App.State_SpecialCircum_IsConsoleWindowIsAttached()) + { + bool bFreeSuccess = Watchdog.WatchdogLib.Win32.Kernel32.FreeConsole(); + App.log.Info(String.Format("CommandLineMode - Free from Console is {0}", bFreeSuccess)); + } + log.Info(AppResx.GetStringFormated("APPLICATION_ENDED_COMMANDLINE_MODE", APPLICATION_NAME_SHORT, APPLICATION_VERSION)); + } + } + + #endregion + } +} diff --git a/@integrate/AppState.cs b/@integrate/AppState.cs new file mode 100644 index 0000000..055defd --- /dev/null +++ b/@integrate/AppState.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Watchdog.WatchdogLib.Other; + +namespace Watchdog +{ + /// + /// Keeps Track of RunTime State Variables/Objects + /// + public static class AppState + { + private static object s_LockObject = new Object(); + + #region Internal State Check Functions + + /// true if the Process Monitor is running, false otherwise + internal static bool IsMonitorRunning() + { + lock (s_LockObject) + { + return AppState.State.GetStateValue(AppState.StateKey.ProcessMonitorStarted_bool, false); + } + } + + /// true if the Scheduler is running, false otherwise + internal static bool IsSchedulerRunning() + { + lock (s_LockObject) + { + return AppState.State.GetStateValue(AppState.StateKey.SchedulerIsRunning_bool, false); + } + } + + /// true if the Process Monitor encountered errors, false otherwise + internal static bool DidMonitorEncounterErrors() + { + lock (s_LockObject) + { + return AppState.State.GetStateValue(AppState.StateKey.ProcessMonitorEncounteredErrors_bool, false); + } + } + + /// true if the Application is called with Command-Line Parameters, false otherwise + internal static bool IsInCommandLinePrmsMode() + { + lock (s_LockObject) + { + return AppState.State.GetStateValue(AppState.StateKey.IsInCommandLineMode_bool, false); + } + } + + /// true if the Application is attached to a Console Window For Output, false otherwise + internal static bool IsAttachedToConsoleWindow() + { + lock (s_LockObject) + { + return AppState.State.GetStateValue(AppState.StateKey.IsAttachedToConsoleWindow_bool, false); + } + } + + /// When the application is in commandline mode, we want to make sure that a 'real' monitor instance is running, + /// in order to communicate with it, this keeps track if that other instance is available + internal static bool InCommandLineMode_IsMonitorInstanceAvailable() + { + lock (s_LockObject) + { + return AppState.State.GetStateValue(AppState.StateKey.InCommandLineMode_IsMonitorInstanceAvailable_bool, false); + } + } + + #endregion + + #region Internal State Set Functions + + /// + /// Use this to set the Monitor is Running state to true/false + /// + internal static bool MonitorIsRunning(bool bIsRunning) + { + lock (s_LockObject) + { + return AppState.State.SetStateValue(AppState.StateKey.ProcessMonitorStarted_bool, bIsRunning); + } + } + + /// + /// Use this to set the Scheduler is Running state to true/false + /// + internal static bool SchedulerIsRunning(bool bIsRunning) + { + lock (s_LockObject) + { + return AppState.State.SetStateValue(AppState.StateKey.SchedulerIsRunning_bool, bIsRunning); + } + } + + /// + /// Use this to set the Monitor's Error State + /// + internal static bool MonitorEncounterErrors(bool bErrorsOccured) + { + lock (s_LockObject) + { + return AppState.State.SetStateValue(AppState.StateKey.ProcessMonitorEncounteredErrors_bool, bErrorsOccured); + } + } + + /// + /// Use this to set that the Application is called with Command-Line Parameters + /// + internal static bool CommandLinePrmsMode(bool bIsInCommandLinePrmsMode) + { + lock (s_LockObject) + { + return AppState.State.SetStateValue(AppState.StateKey.IsInCommandLineMode_bool, bIsInCommandLinePrmsMode); + } + } + + /// + /// Use this to set that the Application is attached to a Console Window for Output + /// + internal static bool AttachedToConsoleWindow(bool bItIsAttached) + { + lock (s_LockObject) + { + return AppState.State.SetStateValue(AppState.StateKey.IsAttachedToConsoleWindow_bool, bItIsAttached); + } + } + + /// + /// Use this to set that the Application can communicate with the 'real' monitor instance that is running. + /// + internal static bool CommandLineMode_IsMonitorInstanceAvailable(bool bIsAvailable) + { + lock (s_LockObject) + { + return AppState.State.GetStateValue(AppState.StateKey.InCommandLineMode_IsMonitorInstanceAvailable_bool, bIsAvailable); + } + } + + #endregion + + /// + /// Various Keys that we can use to save/get the GUI State + /// + internal enum StateKey + { + ProcessMonitorStarted_bool, + ProcessMonitorEncounteredErrors_bool, + SchedulerIsRunning_bool, + IsInCommandLineMode_bool, + IsAttachedToConsoleWindow_bool, + InCommandLineMode_IsMonitorInstanceAvailable_bool, + } + + // Initialize BkgdState + internal static readonly StateM State = new StateM(typeof(StateKey)); + } +} diff --git a/@integrate/CMDlineHandler.cs b/@integrate/CMDlineHandler.cs new file mode 100644 index 0000000..a2dd276 --- /dev/null +++ b/@integrate/CMDlineHandler.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Diagnostics; +using WatchdogLib; +using Watchdog.WatchdogLib.WinForms; +using Watchdog.WatchdogLib.Win32; + +namespace Watchdog +{ + /// + /// Handles CommandLine Logic + /// + public static class CMDlineHandler + { + // Our WCF Client object stub + private static IWatchdog _proxyObj = null; + + /// + /// Shows the Command Line Help either as a Windows Form or + /// to the Console * Depending if a Console Window is Attached * + /// + private static void ShowCommandLineHelp() + { + bool bToConsole = App.State_SpecialCircum_IsConsoleWindowIsAttached(); + string cmdHelpText = ""; + + cmdHelpText += "\n"; + cmdHelpText += "**************************************************************\n"; + cmdHelpText += " Make changes to the .xml Configuration.\n"; + cmdHelpText += "**************************************************************\n"; + cmdHelpText += "-Add_Process \"Application Exe;Prms;WorkingDir\"\n"; + cmdHelpText += "-Remove_Process \"Application Exe;Prms\"\n"; + cmdHelpText += "-Add_Service \"Service Name\"\n"; + cmdHelpText += "-Remove_Service \"Service Name\"\n"; + cmdHelpText += "**************************************************************\n"; + cmdHelpText += " View the .xml Configuration.\n"; + cmdHelpText += "**************************************************************\n"; + cmdHelpText += "-Show_Processes\n"; + cmdHelpText += "-Show_Services\n"; + cmdHelpText += "**************************************************************\n"; + cmdHelpText += " Manipulate a Running Monitor Instance.\n"; + cmdHelpText += "**************************************************************\n"; + cmdHelpText += "-Start\n"; + cmdHelpText += "-Pause\n"; + cmdHelpText += "-Restart\n"; + cmdHelpText += "-Restart_All\n"; + cmdHelpText += "-Stop\n"; + cmdHelpText += "-Stop_All\n"; + cmdHelpText += "**************************************************************\n"; + cmdHelpText += "-? Show Help.\n\n"; + + // Show Help to the User (Either to the console or a Message Window) + if (bToConsole) + Console.Write(cmdHelpText); + else + MsgBox.ShowInfo(cmdHelpText, (App.APPLICATION_NAME_SHORT + " Command Line Help"), System.Windows.Forms.MessageBoxButtons.OK); + } + + /// + /// Either writes out to the console or shows a message box, if we want to alert the user + /// + /// string to write out + private static void WriteOut(string str) + { + bool bToConsole = App.State_SpecialCircum_IsConsoleWindowIsAttached(); + if (bToConsole) + Console.WriteLine("\n" + str); + else + MsgBox.ShowInfo(str, (App.APPLICATION_NAME_SHORT + " Command Line Alert"), System.Windows.Forms.MessageBoxButtons.OK); + } + + /// + /// Main Entry Point for CommandLine Mode * When Application is being called with + /// CommandLine Parameters * + /// + static public void EnterCommandLineMode() + { + // Show Help? + if (App.cmdline.ShowHelp || !App.cmdline.ParamsValid) + { + ShowCommandLineHelp(); + return; + } + + // Check now using WCF... + // Now we need to check communication with the other instance, + // if there isn't another process, then start/stop/restart/etc,.. won't work + _proxyObj = WCFHost.GetWatchDogClientInterface(); + if (_proxyObj != null) + App.State_SpecialCircum_MainMonitorInstance_CommSuccees(); + else + App.log.Info("No other Instances found, Command-line parameter functionallity limited to making configuration changes only"); + + // Now let's go thru all the CommandLine_Flags * WCF * + DealWithApplicationStateWCFCalls_CommandLineFlags(); + + // Now let's go thru all the CommandLine_Flags * Non-WCF * + DealWithDisplayConfigurationNonWCFCalls_CommandLineFlags(); + + // Now let's go thru all the CommandLine_Options * Non-WCF * + DealWithConfigurationChanges_CommandLineOptions(); + + // Done Here + _proxyObj = null; + } + + /// + /// Deals with all the State Changes for the Main Application + /// * Via WCF * calls into main app and makes those changes + /// + private static void DealWithApplicationStateWCFCalls_CommandLineFlags() + { + // Now let's go thru all the CommandLine_Flags * WCF * + if (App.State_SpecialCircum_DidMainMonitorInstanceCommSucceed()) + { + try + { + if (App.cmdline.GetFlagValue(App.CommandLine_Flag.START)) + { + _proxyObj.StartMonitoring(); + } + else if (App.cmdline.GetFlagValue(App.CommandLine_Flag.STOP)) + { + _proxyObj.StopMonitoring(); + } + else if (App.cmdline.GetFlagValue(App.CommandLine_Flag.STOP_ALL)) + { + _proxyObj.StopAllMonitoring(); + } + else if (App.cmdline.GetFlagValue(App.CommandLine_Flag.RESTART)) + { + _proxyObj.RestartMonitoring(); + } + else if (App.cmdline.GetFlagValue(App.CommandLine_Flag.RESTART_ALL)) + { + _proxyObj.RestartAllMonitoring(); + } + else if (App.cmdline.GetFlagValue(App.CommandLine_Flag.PAUSE)) + { + _proxyObj.PauseMonitoring(); + } + } + catch (Exception ex) + { + App.log.Error("Error Occured processing CommandLine_Flag via WCF", ex); + + // Alert the user + WriteOut(ex.Message); + } + } + } + + /// + /// Deals with all the Flags that deal with displaying configuration. does this locally * in this process * + /// View Configuration. Also handles LockWorkstation (Feature). + /// + private static void DealWithDisplayConfigurationNonWCFCalls_CommandLineFlags() + { + if (App.cmdline.GetFlagValue(App.CommandLine_Flag.LOCK)) + { + // Hidden Feature, allows us to specify in windows start-up to lock the workstation + if (!User32.LockWorkStation()) + { + App.log.Error("LockWorkstation() Failed"); + WriteOut("LockWorkstation Failed"); + } + } + else if (App.cmdline.GetFlagValue(App.CommandLine_Flag.SHOW_PROCESSES)) + { + // write out each process + string[] processes = WCFHost.ShowProcesses(); + StringBuilder sb = new StringBuilder(); + if (processes != null && processes.Length != 0) + { + foreach (string p in processes) + { + sb.Append(p); + sb.Append("\n"); + } + } + if (sb.Length > 0) + WriteOut(sb.ToString()); + } + else if (App.cmdline.GetFlagValue(App.CommandLine_Flag.SHOW_SERVICES)) + { + // write out each service + string[] services = WCFHost.ShowServices(); + StringBuilder sb = new StringBuilder(); + if (services != null && services.Length != 0) + { + foreach (string s in services) + { + sb.Append(s); + sb.Append("\n"); + } + } + if (sb.Length > 0) + WriteOut(sb.ToString()); + } + } + + /// + /// Deals with making configuration changes. does this locally * in this process * + /// Add/Remove Configuration + /// + private static void DealWithConfigurationChanges_CommandLineOptions() + { + string ProcessAdd = App.cmdline.GetOptionValue(App.CommandLine_Option.ADD_PROCESS, String.Empty); + if (String.IsNullOrEmpty(ProcessAdd)) + { + string ProcessRemove = App.cmdline.GetOptionValue(App.CommandLine_Option.REMOVE_PROCESS, String.Empty); + if (String.IsNullOrEmpty(ProcessRemove)) + { + string ServiceAdd = App.cmdline.GetOptionValue(App.CommandLine_Option.ADD_SERVICE, String.Empty); + if (String.IsNullOrEmpty(ServiceAdd)) + { + string ServiceRemove = App.cmdline.GetOptionValue(App.CommandLine_Option.REMOVE_SERVICE, String.Empty); + if (!String.IsNullOrEmpty(ServiceRemove)) + { + bool bRemoved = WCFHost.RemoveService(ServiceRemove); + if (bRemoved) + { + WriteOut(String.Format("Service '{0}' removed successfully from Configuration", ServiceRemove)); + App.log.Info(String.Format("Service '{0}' removed successfully from Configuration", ServiceRemove)); + + // * Reload on Main Instance, if possible * + if (App.State_SpecialCircum_DidMainMonitorInstanceCommSucceed()) _proxyObj.ReloadConfigurationNextInterval(); + App.xmlfile.ForceRefreshOnNext_ReadData = true; + } + else + { + WriteOut(String.Format("Service '{0}' failed to be removed from Configuration", ServiceRemove)); + App.log.Error(String.Format("Service '{0}' failed to be removed from Configuration", ServiceRemove)); + } + } + } + else + { + bool bAdded = WCFHost.AddService(ServiceAdd); + if (bAdded) + { + WriteOut(String.Format("Service '{0}' added successfully to Configuration", ServiceAdd)); + App.log.Info(String.Format("Service '{0}' added successfully to Configuration", ServiceAdd)); + + // * Reload on Main Instance, if possible * + if (App.State_SpecialCircum_DidMainMonitorInstanceCommSucceed()) _proxyObj.ReloadConfigurationNextInterval(); + App.xmlfile.ForceRefreshOnNext_ReadData = true; + } + else + { + WriteOut(String.Format("Service '{0}' failed to be added to Configuration", ServiceAdd)); + App.log.Error(String.Format("Service '{0}' failed to be added to Configuration", ServiceAdd)); + } + } + } + else + { + string[] pNc = ProcessRemove.Split(';'); + bool bRemoved = false; + if (pNc.Length == 1) + bRemoved = WCFHost.RemoveProcess(pNc[0], String.Empty); + else if (pNc.Length == 2) + bRemoved = WCFHost.RemoveProcess(pNc[0], pNc[1]); + if (bRemoved) + { + WriteOut(String.Format("Application '{0}' with CommandLinePrms '{1}' removed successfully from Configuration", pNc[0], (pNc.Length > 1) ? pNc[1] : "")); + App.log.Info(String.Format("Application '{0}' with CommandLinePrms '{1}' removed successfully from Configuration", pNc[0], (pNc.Length > 1) ? pNc[1] : "")); + + // * Reload on Main Instance, if possible * + if (App.State_SpecialCircum_DidMainMonitorInstanceCommSucceed()) _proxyObj.ReloadConfigurationNextInterval(); + App.xmlfile.ForceRefreshOnNext_ReadData = true; + } + else + { + WriteOut(String.Format("Application '{0}' with CommandLinePrms '{1}' failed to be removed from Configuration", pNc[0], (pNc.Length > 1) ? pNc[1] : "")); + App.log.Error(String.Format("Application '{0}' with CommandLinePrms '{1}' failed to be removed from Configuration", pNc[0], (pNc.Length > 1) ? pNc[1] : "")); + } + } + } + else + { + string[] pNc = ProcessAdd.Split(';'); + bool bAdded = false; + if(pNc.Length == 1) + bAdded = WCFHost.AddProcess(pNc[0], String.Empty, String.Empty); + else if(pNc.Length == 2) + bAdded = WCFHost.AddProcess(pNc[0], pNc[1], String.Empty); + else if (pNc.Length == 3) + bAdded = WCFHost.AddProcess(pNc[0], pNc[1], pNc[2]); + if (bAdded) + { + WriteOut(String.Format("Application '{0}' with CommandLinePrms '{1}' added successfully to Configuration", pNc[0], (pNc.Length > 1) ? pNc[1] : "")); + App.log.Info(String.Format("Application '{0}' with CommandLinePrms '{1}' added successfully to Configuration", pNc[0], (pNc.Length > 1) ? pNc[1] : "")); + + // * Reload on Main Instance, if possible * + if (App.State_SpecialCircum_DidMainMonitorInstanceCommSucceed()) _proxyObj.ReloadConfigurationNextInterval(); + App.xmlfile.ForceRefreshOnNext_ReadData = true; + } + else + { + WriteOut(String.Format("Application '{0}' with CommandLinePrms '{1}' failed to be added to Configuration", pNc[0], (pNc.Length > 1) ? pNc[1] : "")); + App.log.Error(String.Format("Application '{0}' with CommandLinePrms '{1}' failed to be added to Configuration", pNc[0], (pNc.Length > 1) ? pNc[1] : "")); + } + } + } + + } +} diff --git a/@integrate/CheckActiveTwo.cs b/@integrate/CheckActiveTwo.cs new file mode 100644 index 0000000..f5c746c --- /dev/null +++ b/@integrate/CheckActiveTwo.cs @@ -0,0 +1,482 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Text; +using System.Diagnostics; + +namespace PhoneHome +{ + internal class CheckActiveTwo + { + internal string ProductName = String.Empty; + internal string ProductVersion = String.Empty; + internal string SerialNumber = String.Empty; + private string _LastGeneratedKey = String.Empty; + + /// + /// + /// + /// + /// + /// + internal CheckActiveTwo(string ProductName, string ProductVersion, string SerialNumber) + { + if (!String.IsNullOrEmpty(ProductName) && !String.IsNullOrEmpty(ProductVersion) && !String.IsNullOrEmpty(SerialNumber)) + { + this.ProductName = ProductName; + this.ProductVersion = ProductVersion; + this.SerialNumber = SerialNumber; + } + else + { + throw new ArgumentException("ProductName, ProductVersion, and SerialNumber can't be blank"); + } + } + + /// + /// + /// + /// + internal CheckActiveTwo(string strEncGeneratedString) + { + if (IsValidEncKey(strEncGeneratedString)) + { + _LastGeneratedKey = strEncGeneratedString; + } + else + { + throw new ArgumentException("Not a valid Enc String"); + } + } + + #region Static Internal Utilities + + /// + /// Perform checksum on string + /// + /// + /// Checksum + internal static int PerformChecksum(string strAboutToBeChecksummed) + { + if (!String.IsNullOrEmpty(strAboutToBeChecksummed)) + { + int nChecksum = 0; + for (int i = 0; i < strAboutToBeChecksummed.Length; ++i) + { + if (Char.IsDigit(strAboutToBeChecksummed[i])) + nChecksum = nChecksum + int.Parse(strAboutToBeChecksummed[i].ToString()); + } + return nChecksum; + } + return 0; + } + + /// + /// Dash a String + /// + /// + internal static string MakeIntoDashSeperatedString(string strAboutToBeDashed, int DashEveryNthCharacter) + { + if (!String.IsNullOrEmpty(strAboutToBeDashed)) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < strAboutToBeDashed.Length; i++) + { + if ((i != 0) && ((i % DashEveryNthCharacter) == 0)) + sb.Append("-"); + sb.Append(strAboutToBeDashed[i]); + } + return sb.ToString(); + } + return String.Empty; + } + + /// + /// Undash a String + /// + /// + internal static string MakeIntoDashUnseperatedString(string strAboutToBeUndashed) + { + if (!String.IsNullOrEmpty(strAboutToBeUndashed)) + return strAboutToBeUndashed.Replace("-", ""); + return String.Empty; + } + + #endregion + + #region Internal Methods + + /// + /// Generate a new Key to use - the key can be used to verify the serial number + /// + /// a new Key + internal string GenerateNewKey() + { + // GenerateString For User to send + string KeyValue = MakeKey(); + string EncrKey = EncodeShuffle(KeyValue); + _LastGeneratedKey = MakeIntoDashSeperatedString(EncrKey, 4); + + // For Debugging + string UnEncoded = DecodeShuffle(EncrKey); + if (KeyValue != UnEncoded) + { + // something is terribly wrong with the Encryption + Debug.Assert(false); + } + return _LastGeneratedKey; + } + + /// + /// The function should really be called 'GeneratedPIN' but for reverse engineering + /// purpose, keeping them guessin * security by obscurity * + /// + /// + internal string GeneraledPiM() + { + if (!String.IsNullOrEmpty(_LastGeneratedKey)) + { + int KeyChecksum = PerformChecksum(MakeKey()); + int ShuffledChecksum = PerformChecksum(_LastGeneratedKey); + string Result = KeyChecksum.ToString() + ShuffledChecksum.ToString(); + if (Result.Length < 4) + { + StringBuilder sb = new StringBuilder(Result); + int nRemainder = 4 - Result.Length; + while (nRemainder > 0) { sb.Append("7"); nRemainder--; } + Result = sb.ToString(); + } + Result = Result.Substring(0, 4); + int nHour = DateTime.Now.ToUniversalTime().Hour; + nHour = (nHour <= 6) ? (nHour + 3) : (nHour - 2); + string strHour = String.Format("{0}", (nHour < 10) ? ("8" + nHour.ToString()) : (nHour.ToString())); + string fourdigitPin = (strHour[1] + Result[1].ToString() + strHour[0] + Result[3].ToString()); + int nChecksumPin = PerformChecksum(fourdigitPin); + string strChecksumLastDigit = nChecksumPin.ToString()[nChecksumPin.ToString().Length - 1].ToString(); + return fourdigitPin + strChecksumLastDigit; + } + else + { + GenerateNewKey(); + return GeneraledPiM(); + } + } + + /// + /// + /// + /// + /// + /// + /// + /// true, if successful, false otherwise + internal bool RetrieveValues(out DateTime dtStamp, out string ProductName, out string ProductVersion, out string SerialNumber) + { + dtStamp = DateTime.MinValue; + ProductName = String.Empty; + ProductVersion = String.Empty; + SerialNumber = String.Empty; + if (!String.IsNullOrEmpty(_LastGeneratedKey)) + { + string Undashed = MakeIntoDashUnseperatedString(_LastGeneratedKey); + if (UnmakeKey(DecodeShuffle(Undashed), out dtStamp, out ProductName, out ProductVersion, out SerialNumber)) + { + this.ProductName = ProductName; + this.ProductVersion = ProductVersion; + this.SerialNumber = SerialNumber; + return true; + } + } + return false; + } + + #endregion + + #region Private Key Generation Functions + + private bool ContainsOnlyDigits(string strToCheckForDigits) + { + if (!String.IsNullOrEmpty(strToCheckForDigits)) + { + for (int i = 0; i < strToCheckForDigits.Length; ++i) + { + if (!Char.IsDigit(strToCheckForDigits[i])) + return false; + } + return true; + } + return false; + } + + /// + /// Check to see if the Generated string being passed in is a valid generated string + /// + /// + /// true if valid, false otherwise + private bool IsValidEncKey(string strEncGeneratedString) + { + string Undashed = MakeIntoDashUnseperatedString(strEncGeneratedString); + DateTime dt; + string pName, pVersion, pSerial; + if (UnmakeKey(DecodeShuffle(Undashed), out dt, out pName, out pVersion, out pSerial)) + return true; + return false; + } + + /// + /// Make a Key to send across (all the info the auth needs) + /// + /// Key with needed Info + private string MakeKey() + { + //string dtShortTest = DateTime.Now.ToUniversalTime().ToShortDateString().Replace("/", ""); + DateTime dtUniversal = DateTime.Now.ToUniversalTime(); + string dtMonth = (dtUniversal.Month > 9) ? String.Format("{0}", dtUniversal.Month) : String.Format("0{0}", dtUniversal.Month); + string dtDay = (dtUniversal.Day > 9) ? String.Format("{0}", dtUniversal.Day) : String.Format("0{0}", dtUniversal.Day); + string dtYear = dtUniversal.Year.ToString(); + string dtShort = String.Format("{0}{1}{2}", dtMonth, dtDay, dtYear); + string ProductId = ProductName.Substring(0, 1); // should always be 'M' or 'L' // so we use "LMXYZ" + string strKey = dtShort + "Z" + ProductId + "Y" + ProductVersion.Split('.')[0] + "X" + SerialNumber; + return strKey; + } + + /// + /// Unmake a key * Don't even know why i wrote this, prob. won't end up using this * + /// + /// true if successful, false otheriwise + private bool UnmakeKey(string strAboutToBeUnkeyed, out DateTime dtStamp, out string ProductName, out string ProductVersion, out string SerialNumber) + { + dtStamp = DateTime.MinValue; + ProductName = ""; + ProductVersion = ""; + SerialNumber = ""; + + //string strKey = dtShort + "Z" + ProductId + "Y" + ProductVersion.Split('.')[0] + "X" + SerialNumber; + //0123456Z + try + { + if (!String.IsNullOrEmpty(strAboutToBeUnkeyed)) + { + int nZIndex = strAboutToBeUnkeyed.IndexOf("Z"); + int nYIndex = strAboutToBeUnkeyed.IndexOf("Y"); + int nXIndex = strAboutToBeUnkeyed.IndexOf("X"); + if (nZIndex == -1 || nYIndex == -1 || nXIndex == -1) + return false; + + // dtShort + string strDT = strAboutToBeUnkeyed.Substring(0, nZIndex); + strDT = String.Format("{0}/{1}/{2}", strDT.Substring(0, 2), strDT.Substring(2, 2), strDT.Substring(4)); + dtStamp = DateTime.Parse(strDT); + + // ProductId + string ProductId = strAboutToBeUnkeyed.Substring(nZIndex + 1, 1); + if (ProductId == "L") + ProductName = "Lytec"; + else if (ProductId == "M") + ProductName = "Medisoft"; + else + return false; + + // ProductVersion + string strProductVersion = strAboutToBeUnkeyed.Substring(nYIndex + 1, (nXIndex - nYIndex - 1)); + if (!String.IsNullOrEmpty(strProductVersion) && ContainsOnlyDigits(strProductVersion)) + ProductVersion = strProductVersion; + else + return false; + + // Serial Number + SerialNumber = strAboutToBeUnkeyed.Substring(nXIndex + 1); + return !String.IsNullOrEmpty(SerialNumber) && ContainsOnlyDigits(SerialNumber); + } + } + catch (Exception) { /* ignore */ } + return false; + } + + /// + /// * Simple Encoder * + /// + /// + /// + private string EncodeShuffle(string strAboutToBeEncodeShuffled) + { + const string Char_CodeLib = "ABCDEFGHIJKNOPQRSTUVW"; //20 - W is never used + const string Char_CodeLibExcluded = "LMXYZ"; //5 + + if (!String.IsNullOrEmpty(strAboutToBeEncodeShuffled)) + { + List ResultStr = new List(strAboutToBeEncodeShuffled); + int nCount = ResultStr.Count; + + // Every N'th Digit do something + for (int i = 0; i < nCount; i = i + 3) + { + char c = ResultStr[i]; + if (char.IsDigit(c)) + { + int nChar = int.Parse(c.ToString()); + ResultStr[i] = Char_CodeLib[nChar]; // 0..9 + } + } + + // Every N'th Digit do something + for (int i = 0; i < nCount; i = i + 4) + { + char c = ResultStr[i]; + if (char.IsDigit(c)) + { + int nChar = int.Parse(c.ToString()); + ResultStr[i] = Char_CodeLib[nChar + 10]; // 10..19 + } + } + + // Add Randomness to the end of the string + Random random = new Random(); + int nRand = random.Next(1, 9); + + // Perform a Random Shift * So that the code ALWAYS looks different from use to use * + for (int i = 0; i < nCount; i = i + 2) + { + char c = ResultStr[i]; + if (char.IsLetter(c) && !Char_CodeLibExcluded.Contains(c.ToString())) + { + int nIndexShifted = Char_CodeLib.IndexOf(c) + nRand; + int nIndexShiftedAdj = nIndexShifted % 21; + ResultStr[i] = Char_CodeLib[nIndexShiftedAdj]; // 0..20 + } + } + + // Perform another Random Swap * So that the code ALWAYS looks different from use to use * + for (int i = 0; i < nCount; i = i + nRand) + { + char c = ResultStr[i]; + int nOpposite = nCount - i - 1; + char o = ResultStr[nOpposite]; + if (char.IsLetter(c) && !Char_CodeLibExcluded.Contains(c.ToString()) && + char.IsLetter(o) && !Char_CodeLibExcluded.Contains(o.ToString())) + { + // swap + ResultStr[i] = o; + ResultStr[nOpposite] = c; + } + } + + // Perform a Reversal + for (int i = 0; i < (nCount / 2); ++i) + { + char N1 = ResultStr[i]; + char N2 = ResultStr[nCount - 1 - i]; + // swap + ResultStr[i] = N2; + ResultStr[nCount - 1 - i] = N1; + } + + // Add the Randomness to the string for proper decoding to occur + ResultStr.Add(Char.Parse(nRand.ToString())); + + // And Return + return new String(ResultStr.ToArray()); + } + return String.Empty; + } + + /// + /// * Simple Decoder * + /// + /// + /// + private string DecodeShuffle(string strAboutToBeDecodeShuffled) + { + const string Char_CodeLib = "ABCDEFGHIJKNOPQRSTUVW"; //20 + const string Char_CodeLibExcluded = "LMXYZ"; //5 + try + { + if (!String.IsNullOrEmpty(strAboutToBeDecodeShuffled)) + { + List ResultStr = new List(strAboutToBeDecodeShuffled); + + // retrieve Randomness Factor + char cLast = ResultStr[ResultStr.Count - 1]; + ResultStr.RemoveAt(ResultStr.Count - 1); + int nCount = ResultStr.Count; + int nRand = int.Parse(cLast.ToString()); + + // Perform a Reversal + for (int i = 0; i < (nCount / 2); ++i) + { + char N1 = ResultStr[i]; + char N2 = ResultStr[nCount - 1 - i]; + // swap + ResultStr[i] = N2; + ResultStr[nCount - 1 - i] = N1; + } + + // Perform another Random Swap * So that the code ALWAYS looks different from use to use * + for (int i = 0; i < nCount; i = i + nRand) + { + char c = ResultStr[i]; + int nOpposite = nCount - i - 1; + char o = ResultStr[nOpposite]; + if (char.IsLetter(c) && !Char_CodeLibExcluded.Contains(c.ToString()) && + char.IsLetter(o) && !Char_CodeLibExcluded.Contains(o.ToString())) + { + // swap + ResultStr[i] = o; + ResultStr[nOpposite] = c; + } + } + + // Perform a Random Shift * So that the code ALWAYS looks different from use to use * + for (int i = 0; i < nCount; i = i + 2) + { + char c = ResultStr[i]; + if (char.IsLetter(c) && !Char_CodeLibExcluded.Contains(c.ToString())) + { + int nIndexShifted = Char_CodeLib.IndexOf(c) - nRand; + int nIndexShiftedAdj = (nIndexShifted < 0)? 21 + nIndexShifted : nIndexShifted; + + ResultStr[i] = Char_CodeLib[nIndexShiftedAdj]; // 0..20 + } + } + + // Every N'th Digit do something + for (int i = 0; i < nCount; i = i + 4) + { + char c = ResultStr[i]; + if (char.IsLetter(c) && !Char_CodeLibExcluded.Contains(c.ToString())) + { + int nIndex = Char_CodeLib.IndexOf(c); + if (nIndex >= 10) + { + nIndex = nIndex - 10; + if (nIndex >= 0 && nIndex <= 9) + ResultStr[i] = Char.Parse(nIndex.ToString()); // 11..19 + } + } + } + + // Every N'th Digit do something + for (int i = 0; i < nCount; i = i + 3) + { + char c = ResultStr[i]; + if (char.IsLetter(c)) + { + int nIndex = Char_CodeLib.IndexOf(c); + if (nIndex >= 0 && nIndex <= 9) + ResultStr[i] = Char.Parse(nIndex.ToString()); // 1..9 + } + } + + // And Return + return new String(ResultStr.ToArray()); + } + } + catch (Exception) { /* ignore */ } + return String.Empty; + } + + #endregion + } + +} diff --git a/@integrate/ClickOnceUpdater.cs b/@integrate/ClickOnceUpdater.cs new file mode 100644 index 0000000..faa0c43 --- /dev/null +++ b/@integrate/ClickOnceUpdater.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Deployment.Application; +using Forms = System.Windows.Forms; +using System.Windows; +using System.Diagnostics; +using Watchdog.WatchdogLib.WinForms; +using System.IO; + +namespace Watchdog +{ + /// + /// Responsible for Dynamically Checking if there is an update available via ClickOnce + /// + public static class ClickOnceUpdater + { + /// + /// Construction + /// + static ClickOnceUpdater(){} + + /// + /// Installs the Update and Restarts the Current Instance + /// + /// false if an error occured, if succesful * Will Restart the App * + internal static bool InstallUpdateAndRestartIfSuccessful() + { + try + { + UpdateCheckInfo info = null; + ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment; + if (ad != null) + { + // Log this to make sure we are sane + App.log.Info("Success in retrieving Application Deployment Manifest. This is a ClickOnce App"); + + try + { + info = ad.CheckForDetailedUpdate(); + } + catch (DeploymentDownloadException dde) + { + MsgBox.ShowInfo("The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " + dde.Message, "Unable to Download", System.Windows.Forms.MessageBoxButtons.OK); + App.log.Info("The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " + dde.Message); + return false; + } + catch (InvalidDeploymentException ide) + { + MsgBox.ShowError("Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " + ide.Message, "Invalid Deployment", System.Windows.Forms.MessageBoxButtons.OK); + App.log.Error("Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " + ide.Message); + return false; + } + catch (InvalidOperationException ioe) + { + MsgBox.ShowError("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message, "Invalid Operation", System.Windows.Forms.MessageBoxButtons.OK); + App.log.Error("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message); + return false; + } + + if (info.UpdateAvailable) + { + Boolean doUpdate = true; + + if (!info.IsUpdateRequired) + { + Forms.DialogResult dr = MsgBox.ShowInfo("An update is available. Would you like to update\nthe application now?", "Update Available", Forms.MessageBoxButtons.OKCancel); + if (!(Forms.DialogResult.OK == dr)) + doUpdate = false; + } + else + { + // Display a message that the app MUST reboot. Display the minimum required version. + MsgBox.ShowInfo("This application has detected a mandatory update from your\ncurrent " + + "version to version " + info.MinimumRequiredVersion.ToString() + + ".\nThe application will now install\nthe update and restart.", + "Update Available", System.Windows.Forms.MessageBoxButtons.OK); + App.log.Info("This application has detected a mandatory update from your current " + + "version to version " + info.MinimumRequiredVersion.ToString() + + ". The application will now install the update and restart."); + } + + if (doUpdate) + { + try + { + ad.Update(); + App.log.Info("The application has been upgraded,and will now restart."); + + // Restart the Application * Imp! Auto-Delay the Start of the new Watchdog Instance * + if(File.Exists(App.APPLICATION_CLICKONCE_STARTMENU_LINK)) + Process.Start(App.APPLICATION_CLICKONCE_STARTMENU_LINK, "15"); + + // Auto-Update * No Need to Close Applications * + App.State_SpecialCircum_DontCloseApplicationsOnExit(); + Application.Current.Shutdown(); + return true; + } + catch (DeploymentDownloadException dde) + { + MsgBox.ShowError("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde, "Update Error", System.Windows.Forms.MessageBoxButtons.OK); + App.log.Error("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde); + return false; + } + } + } + else + { + App.log.Info("Newer Version not available at this time."); + MsgBox.ShowInfo("A newer version of the application\nis not available at this time. \n\nPlease try again later.", "Newer Version not available", System.Windows.Forms.MessageBoxButtons.OK); + } + } + + } + catch (Exception) { /* ignore */ } + return false; + } + + } +} diff --git a/@integrate/Component.All/Common.cs b/@integrate/Component.All/Common.cs new file mode 100644 index 0000000..7cb381f --- /dev/null +++ b/@integrate/Component.All/Common.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using BridgeConnector.Lib.Process; +using BridgeConnector.Lib.Tools; +using System.IO; +using BridgeConnector.Lib.File; +using BridgeConnector.Lib.XML; +using System.Reflection; + +namespace Component.All +{ + /// + /// Common Functions and Helpers Useful for all Installing activities. + /// + public static class Common + { + #region Public Definitions + + public const string INSTALLED_COMPONENT_CONFIG_XML_FILENAME = "InstalledComponentConfig.xml"; + public const string EMBEDDED_COMPONENT_CONFIG_XML_FILENAME = "EmbeddedComponentConfig.xml"; + + /// + /// Allow Setting of Log by external Assembly + /// + public static Logging Log + { + get + { + return s_Log; + } + set + { + if (value != null) + s_Log = value; + } + } + + #endregion + + #region Private Statics + + private static ISReadWrite s_isrw = null; + private static XSerializer s_serializer = null; + private static ComponentConfig s_EmbeddedComponentConfig = null; + private static ComponentConfig s_InstalledComponentConfig = null; + private static Logging s_Log = null; + + #endregion + + #region Construction + + /// + /// Responsible for reading in embedded and installed configuration + /// + static Common() + { + s_isrw = new ISReadWrite(INSTALLED_COMPONENT_CONFIG_XML_FILENAME); + s_serializer = new XSerializer(); + + //# Read in from Resource (Embedded Components) + s_EmbeddedComponentConfig = s_serializer.ReadFromResource(Assembly.GetExecutingAssembly().GetManifestResourceStream("Component.All." + EMBEDDED_COMPONENT_CONFIG_XML_FILENAME)); + if (s_EmbeddedComponentConfig == null) + throw new Exception("Could not read in EmbeddedComponentConfig"); // should never happen + + //# Read in from IS (Currently Installed Components) + s_InstalledComponentConfig = s_isrw.ReadFromIS(); + if (s_InstalledComponentConfig == null) + s_InstalledComponentConfig = new ComponentConfig(); + } + + #endregion + + #region Public Statics + + /// + /// Retrieve the EmbeddedComponentConfig + /// + /// the EmbeddedComponentConfig + public static ComponentConfig EmbeddedConfig + { + get + { + return s_EmbeddedComponentConfig; + } + } + + /// + /// Retrieve the InstalledComponentConfig + /// + /// the InstalledComponentConfig or null, if not existent + public static ComponentConfig InstalledConfig + { + get + { + return s_InstalledComponentConfig; + } + } + + /// + /// Allows Caller to write out any changes to InstalledConfig back to the File + /// + public static void WriteOutChangesToInstalledConfig() + { + s_isrw.WriteToIS(s_InstalledComponentConfig); + } + + /// + /// Checks to see if any Components are installed. If this returns false, then this is a Fresh Install + /// + /// true, if any components are installed, false otherwise + public static bool AreAnyComponentsInstalled() + { + bool bIsInstalled = (InstalledConfig != null) && (InstalledConfig.BinaryComponents.Components.Length > 0 || InstalledConfig.SetupComponents.Components.Length > 0); + return bIsInstalled; + } + + /// + /// Retrieves the Index for the Component that matches the specified Unique Label + /// + /// label to search components for + /// a component array + /// a value >=0 or -1, if not found + public static int GetIndexForComponentUniqueLabel(string UniqueLabel, ComponentConfig.Component[] components) + { + if (String.IsNullOrEmpty(UniqueLabel) || components == null || components.Length <= 0) + return -1; + + for (int i = 0; i < components.Length; ++i) + { + if (String.Compare(components[i].UniqueLabel, UniqueLabel, true) == 0) + return i; + } + return -1; + } + + /// + /// Spawn a Setup Process (Setup.exe for example) + /// + public static void PSetupSpwan(string SetupFileNameNPath, string param_s) + { + PStarter.StartProcess(PStartInfo.CreateProcess(SetupFileNameNPath, param_s, "", true, System.Diagnostics.ProcessWindowStyle.Hidden, false), true, false); + } + + /// + /// Spawn a MSI Setup Process (*.msi) + /// + public static void PSetupMSIEXEC(string param_s) + { + string msiexec = System.Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\msiexec.exe"; + PStarter.StartProcess(PStartInfo.CreateProcess(msiexec, param_s, "", true, System.Diagnostics.ProcessWindowStyle.Hidden, false), true, false); + } + + /// + /// Run a command on the commandline * Hidden * + /// + /// cmd to run + public static string RunCmdLine(string cmdline) + { + string result = PStarter.RunDosCommand(cmdline); + return result; + } + + /// + /// Use this to get the complete path to a .net framework utility like gacutil.exe or + /// installutil.exe.. any .net framework utility. Will fall back to look in the %temp% folder, + /// if not found, giving the opportunity to deploy the util directly with the components + /// + /// the utility in .net you are looking for like gacutil.exe + /// the full filenameNpath or "", if no existing file found + public static string GetNetFrameworkUtilFileNameNPathFile(string UtilFileName) + { + string windir = System.Environment.GetEnvironmentVariable("windir", EnvironmentVariableTarget.Machine); + string NetFramework1_0 = windir + "\\Microsoft.Net\\Framework\\v1.0.3705"; + string NetFramework1_1 = windir + "\\Microsoft.Net\\Framework\\v1.1.4322"; + string NetFramework2_0 = windir + "\\Microsoft.Net\\Framework\\v2.0.50727"; + string NetFramework3_0 = windir + "\\Microsoft.Net\\Framework\\v3.0"; + string NetFramework3_5 = windir + "\\Microsoft.Net\\Framework\\v3.5"; + string NetFramework4_0 = windir + "\\Microsoft.Net\\Framework\\v4.0.30319"; + string TempPath = PathNaming.PathEndsWithNoSlash(Path.GetTempPath()); // We use this as a Fallback, in case file doesn't exist in the framework + string[] Frameworks = new string[] { NetFramework2_0, NetFramework4_0, NetFramework1_0, NetFramework1_1, NetFramework3_0, NetFramework3_5, TempPath }; + + string NetUtilFileNameNPath = ""; + foreach (string framework in Frameworks) + { + if (File.Exists(framework + "\\" + UtilFileName)) + { + NetUtilFileNameNPath = framework + "\\" + UtilFileName; + return NetUtilFileNameNPath; + } + }; + return NetUtilFileNameNPath; + } + + /// + /// Quick Helper to get the Program Files Path for the specific system + /// + /// Program Files path with a '/' at the end + public static string GetProgramFilesPathOnSystemWithEndSlash() + { + string ProgramFiles = System.Environment.GetEnvironmentVariable("ProgramFiles(x86)"); + if (String.IsNullOrEmpty(ProgramFiles)) + ProgramFiles = System.Environment.GetEnvironmentVariable("ProgramFiles"); + return PathNaming.PathEndsWithSlash(ProgramFiles); + } + + /// + /// + /// + /// + public static string SetOwnership() + { + // in order to do any of this, sadly, we must first install the windows resource kit + //subinacl /subdirectories *.* /setowner=domainname\user + return String.Empty; + } + + /// + /// To grant the specified User or group Full Control permissions to the folder and its contents + /// + /// full path to folder/directory + /// domainname\administrator, any windows user or group + /// + public static bool GrantFullPermissionToFolderForUserOrGroup(string FolderNPath, string UserOrGroup) + { + if (Directory.Exists(FolderNPath)) + { + string command = String.Format("cacls \"{0}\" /t /e /g {1}:f", FolderNPath, UserOrGroup); + if (command.Contains("Invalid arguments.")) + return false; + else + return true; + } + return false; + } + + /// + /// Stop a service + /// + /// name of service to stop + /// true if successful, false otherwise + public static bool StopService(string ServiceName) + { + bool bSuccess = true; + if (ServiceW.DoesServiceExist(ServiceName)) + bSuccess = ServiceW.StopService(ServiceName, true, 120); + return bSuccess; + } + + /// + /// start a service + /// + /// name of a service to start + /// true if successful, false otherwise + public static bool StartService(string ServiceName) + { + bool bSuccess = true; + if (ServiceW.DoesServiceExist(ServiceName)) + bSuccess = ServiceW.StartService(ServiceName, 120); + return bSuccess; + } + + /// + /// Does Service Exist + /// + /// Name of service to check + /// true if successful, false otherwise + public static bool ServiceExists(string ServiceName) + { + return ServiceW.DoesServiceExist(ServiceName); + } + + #endregion + } +} diff --git a/@integrate/Component.All/Common_MediLytec.cs b/@integrate/Component.All/Common_MediLytec.cs new file mode 100644 index 0000000..7e0353b --- /dev/null +++ b/@integrate/Component.All/Common_MediLytec.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Win32; + +namespace Component.All +{ + /// + /// Install Configuration + /// + public enum InstallConfig + { + LytecMD, + MedisoftClinical, + } + + /// + /// Common Functions and Helpers Useful for Lytec, Medisoft Installing activities + /// + public static class Common_MediLytec + { + /// + /// Common/Ofted used #Defs/Definitions associated with Lytec/Medisoft + /// + public static class MediLytecPoundDef + { + public const string BRIDGE_SERVICE_TITLE = "BridgeService"; + public const string BRIDGE_SERVICE_ASSEMBLY = "BridgeService.exe"; + public const string BRIDGE_SERVICE_NAME = "McKesson Bridge Service"; + public const string MIRTH_SERVICE_NAME = "Mirth Connect Service"; + public const string POSTGRE_SERVICE_NAME = "Mirth_Connect_PostgreSQL_Server"; + public const string POSTGRE_SERVER_PORT = "5432"; + } + + /// + /// Retrieve the Configuration (Lytec/Medisoft) from the Registry + /// + /// Lytec/Medisoft + public static InstallConfig RetrieveInstallConfigFromRegistry() + { + bool bIsLytecInstalled = false; + try + { + RegistryKey reg = Registry.LocalMachine.OpenSubKey("Software\\Lytec", false); + bIsLytecInstalled = (reg != null); + if (!bIsLytecInstalled) // also check Wow64 + { + reg = Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\Lytec", false); + bIsLytecInstalled = (reg != null); + } + + } + catch (Exception) { /* ignore */ } + + if (bIsLytecInstalled) + return InstallConfig.LytecMD; + else + return InstallConfig.MedisoftClinical; + } + } +} diff --git a/@integrate/Component.All/Component.All.csproj b/@integrate/Component.All/Component.All.csproj new file mode 100644 index 0000000..1b98bcc --- /dev/null +++ b/@integrate/Component.All/Component.All.csproj @@ -0,0 +1,79 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {94CB1BBA-5CF1-4155-827E-1527032D3921} + Library + Properties + Component.All + Component.All + v3.5 + 512 + + + true + full + false + ..\..\Output\Debug\Components\ + DEBUG;TRACE + prompt + 4 + x86 + + + pdbonly + true + ..\..\Output\Release\Components\ + TRACE + prompt + 4 + x86 + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + + + + + + + + + + + + + + + {9F60FBD1-0DA0-4558-971F-5BA9EE44493A} + BridgeConnectorLib + False + + + + + + + + \ No newline at end of file diff --git a/@integrate/Component.All/ComponentConfig.cs b/@integrate/Component.All/ComponentConfig.cs new file mode 100644 index 0000000..7eb5d1e --- /dev/null +++ b/@integrate/Component.All/ComponentConfig.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; +using System.Collections; +using BridgeConnector.Lib.Tools; +using System.IO; + +namespace Component.All +{ + /// + /// Serializable Xml Object used to store all Configuration data + /// + [XmlRoot("ComponentConfig", Namespace = "BridgeConnect", IsNullable = false)] + public class ComponentConfig + { + public ComponentW BinaryComponents = null; + public ComponentW SetupComponents = null; + + /// + /// XML Embedded Component Configuration + /// + public ComponentConfig() + { + BinaryComponents = new ComponentW(); + SetupComponents = new ComponentW(); + } + + /// + /// Wrapper class for multiple Components + /// + public class ComponentW + { + private ArrayList m_ArrayList; + public ComponentW() + { + m_ArrayList = new ArrayList(); + } + + [XmlElement("Component")] + public Component[] Components + { + get + { + Component[] components = new Component[m_ArrayList.Count]; + m_ArrayList.CopyTo(components); + return components; + } + set + { + if (value == null) return; + Component[] components = (Component[])value; + m_ArrayList.Clear(); + foreach (Component component in components) + AddUpdateComponent(component.UniqueLabel, component.Version, component.FileName); + } + } + + #region Public Helpers + + /// + /// Call this function to Add/Update a component + /// + /// unique label used to identify a component + /// Version of the component + /// FileName of the component + public void AddUpdateComponent(string UniqueLabel, string Version, string FileName) + { + int nIndex = GetIndexForComponent(UniqueLabel); + if (nIndex != -1) + { + Component component = ((Component)m_ArrayList[nIndex]); + component.UniqueLabel = UniqueLabel; + component.Version = Version; + component.FileName = FileName; + } + else + { + m_ArrayList.Add(new Component(UniqueLabel, Version, FileName)); + } + } + + /// + /// Call this function to remove a component from the list + /// + /// unique label used to identify a component + public void RemoveComponent(string UniqueLabel) + { + int nIndex = GetIndexForComponent(UniqueLabel); + if (nIndex != -1) + m_ArrayList.RemoveAt(nIndex); + } + + /// + /// Checks to see if a component already exists + /// + /// unique name identifying the component + /// true for yes, no otherwise + public bool ComponentExists(string UniqueLabel) + { + return (GetIndexForComponent(UniqueLabel) != -1); + } + + /// + /// Retrieves the component for the specified UniqueLabel + /// + /// unique name identifying the component + /// the Component for the Label, or null if not found + public Component GetComponent(string UniqueLabel) + { + int nIndex = GetIndexForComponent(UniqueLabel); + if (nIndex != -1) + return (Component) m_ArrayList[nIndex]; + else + return null; + } + + #endregion + + #region Internal & Private Helpers + + /// + /// gets the index in the array list for the specified component + /// + /// unique name identifying the component + /// index >= 0 or -1 if not found + private int GetIndexForComponent(string UniqueLabel) + { + for (int i = 0; i < m_ArrayList.Count; ++i) + { + Component component = (Component)m_ArrayList[i]; + if (String.Compare(component.UniqueLabel, UniqueLabel, true) == 0) + return i; + } + return -1; + } + + #endregion + } + + /// + /// specify the Component + /// + public class Component : IComparable + { + public Component() { } + public Component(string UniqueLabel, string Version, string FileName) { this.UniqueLabel = UniqueLabel; this.Version = Version; this.FileName = FileName; } + + [XmlText] + public string FileName = ""; + + /// + /// In case a component has multiple files, seperated by a ';', internally we should always call this + /// + public string[] FileNames + { + get + { + if (!String.IsNullOrEmpty(FileName)) + { + if (FileName.Contains(';')) + return FileName.Split(';'); + else + return new string[] { FileName }; + } + return new string[] { }; + } + } + + [XmlAttribute("UniqueLabel")] + public string UniqueLabel = ""; + + [XmlAttribute("Version")] + public string Version = ""; + + /// + /// In case a component has multiple files, seperated by a ';', internally we should always call this + /// + public string[] TempFileNamesNPath + { + get + { + string[] files = FileNames; + List tFiles = new List(); + if (files != null) + { + string strPath = PathNaming.PathEndsWithSlash(Path.GetTempPath()); + foreach (string file in files) + tFiles.Add(strPath + file); + return tFiles.ToArray(); + } + return new string[] { }; + } + } + + #region IComparable Members + + /// + /// Compares the Components Unique Label and Version + /// + /// + /// + public int CompareTo(object obj) + { + if (obj is Component) + { + Component c = (Component)obj; + int nCompare = String.Compare(this.UniqueLabel, c.UniqueLabel, true); + if (nCompare == 0) + nCompare = String.Compare(this.Version, c.Version, true); + return nCompare; + } + else + { + throw new ArgumentException("object is not a Component"); + } + } + + #endregion + } + } +} diff --git a/@integrate/Component.All/Component_Binary_Manager.cs b/@integrate/Component.All/Component_Binary_Manager.cs new file mode 100644 index 0000000..23b6ac6 --- /dev/null +++ b/@integrate/Component.All/Component_Binary_Manager.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using BridgeConnector.Lib.File; +using System.Xml.Serialization; +using System.Collections; +using BridgeConnector.Lib.XML; +using System.Resources; +using System.Reflection; +using System.IO; +using BridgeConnector.Lib.Tools; +using BridgeConnector.Lib.Assembly; + +namespace Component.All +{ + /// + /// Responsible for extracting the Components that are embedded in this dll + /// into the temporary directory for the installer to consume + /// + public class Component_Binary_Manager : IDisposable, IManageComponents + { + #region Private Members + + private bool _disposed = false; + + private Dictionary _componentsLoaded = new Dictionary(); + + #endregion + + #region Construction + + /// + /// Constructor + /// + public Component_Binary_Manager() + { + } + + /// + /// Finalizer + /// + ~Component_Binary_Manager() + { + Dispose(true); + } + + #endregion + + #region IManageComponents Members + + /// + /// Checks if there are Newer Components embedded then that were installed on the system + /// + /// true, if newer components were found, false otherwise + public bool AreNewerComponentsAvailable() + { + //# If nothing is installed, no need to continue + if (GetAllInstalledComponents() == null) + return true; + + // if the lengths don't match, something must get installed + if (GetAllInstalledComponents().Length != GetAllEmbeddedComponents().Length) + return true; + + // # Otherwise, let's determine 1 by 1 + foreach (ComponentConfig.Component component in GetAllEmbeddedComponents()) + { + int nIndex = Common.GetIndexForComponentUniqueLabel(component.UniqueLabel, GetAllInstalledComponents()); + if (nIndex == -1) + return true; + else if(GetAllInstalledComponents()[nIndex].CompareTo(component) != 0) + return true; + } + return false; + } + + /// + /// Returns the Component Definition containing the temporary file (extracted component) + /// for all Newer components found on the system + /// + /// Setup Event Object is passed, for component manager to pass down to it's components + /// a list of newer binary components, or empty list for none + public IInstallComponent[] GetNewerComponents(ref SetupEvents setupEventObj) + { + // # Extract all, or let's determine 1 by 1 and extract + bool bInstalledCompsFound = (GetAllInstalledComponents() != null); + foreach (ComponentConfig.Component component in GetAllEmbeddedComponents()) + { + bool bExtract = true; + if (bInstalledCompsFound) + { + int nIndex = Common.GetIndexForComponentUniqueLabel(component.UniqueLabel, GetAllInstalledComponents()); + if (nIndex != -1) + bExtract = (GetAllInstalledComponents()[nIndex].CompareTo(component) != 0); + } + + // mark component for Installation * Extract to File System * + if (bExtract) + { + if (!ExtractComponentFromResourceToTempFileLocation(component)) + Common.Log.Error(String.Format("Failed to Extract Component {0}", component.UniqueLabel)); + } + } + + List ComponentsToInstall = new List(); + if (_componentsLoaded.Count > 0) + { + foreach(Assembly asm in _componentsLoaded.Values) + { + IInstallComponent installComp = null; + Type[] types = asm.GetTypes(); + foreach (Type t in types) + { + if (t.GetInterface(typeof(IInstallComponent).Name) != null) + { + installComp = (IInstallComponent) asm.CreateInstance(t.FullName); + break; + } + } + + // Check IInstallComponent was found + if (installComp == null) + { + Common.Log.Error(String.Format("Component {0} contains no IInstallComponent Definition.", AssemblyW.GetAssemblyName(asm))); + continue; + } + + // Check if class also implements ISetup + if(installComp is ISetup) + ((ISetup) installComp).ComponentLoaded(ref setupEventObj); + + // Add to Install Components + ComponentsToInstall.Add(installComp); + } + } + return ComponentsToInstall.ToArray(); + } + + /// + /// Retrieves the list of all installed components, in order to uninstall + /// + /// a list of all installed components, null otherwise + public IInstallComponent[] GetAllInstalledComponents(string ComponentsSeperatedbySemiColonOrBlankForAll, ref SetupEvents setupEventObj) + { + // TO DO: + return null; + } + + /// + /// Retrieves the list of all installed components + /// + /// a list of all installed components, null otherwise + public ComponentConfig.Component[] GetAllInstalledComponents() + { + if (Common.InstalledConfig != null && Common.InstalledConfig.BinaryComponents.Components.Length > 0) + return Common.InstalledConfig.BinaryComponents.Components; + return null; + } + + /// + /// Retrieves the list of all installed componentsW + /// + /// a list of all installed componentsW, null otherwise + public ComponentConfig.ComponentW GetAllInstalledComponentsW() + { + return Common.InstalledConfig.BinaryComponents; + } + + /// + /// Retrieves the list of all Embedded components + /// + /// a list of all embedded components, null otherwise + public ComponentConfig.Component[] GetAllEmbeddedComponents() + { + if (Common.EmbeddedConfig != null && Common.EmbeddedConfig.BinaryComponents.Components.Length > 0) + return Common.EmbeddedConfig.BinaryComponents.Components; + return null; + } + + #endregion + + #region Private Helpers + + /// + /// Private Helper to physically extract the bits from the resource and write them to a temporary + /// file location + /// + /// Component, whose files are to be extracted + /// true, if successful, false otherwise + private bool ExtractComponentFromResourceToTempFileLocation(ComponentConfig.Component component) + { + if (component != null) + { + // Extract the component to the Temp Directory, + // if it is not already there... + for (int i = 0; i < component.FileNames.Length; ++i) + { + // First try loading the assembly + string asmName = "Component.Binary." + component.UniqueLabel; + Assembly asm = Assembly.Load(asmName); + if (asm == null) + return false; + else + _componentsLoaded[component.UniqueLabel] = asm; // <- imp + string FileName = component.FileNames[i]; + string TempFileNameNPath = component.TempFileNamesNPath[i]; + if (!File.Exists(TempFileNameNPath)) + { + using (BinaryReader br = new BinaryReader(asm.GetManifestResourceStream(asmName + "." + FileName))) + using (BinaryWriter bw = new BinaryWriter(new FileStream(TempFileNameNPath, FileMode.Create))) + { + byte[] buffer = new byte[64 * 1024]; + int numread = br.Read(buffer, 0, buffer.Length); + while (numread > 0) + { + bw.Write(buffer, 0, numread); + numread = br.Read(buffer, 0, buffer.Length); + } + bw.Flush(); + } + } + } + return true; + } + return false; + } + + #endregion + + #region IDisposable Members + + /// + /// Dispose of all extracted files + /// + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer + GC.SuppressFinalize(this); + } + + /// + /// Make sure to conserve disk space, to delete all files that were extracted + /// by this program + /// + /// if true, delete all files extracted by this dll + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + // When Disposing, Delete all Temporary Files on the System that may exist + foreach (ComponentConfig.Component component in GetAllEmbeddedComponents()) + { + foreach (string tFileNameNPath in component.TempFileNamesNPath) + { + if (File.Exists(tFileNameNPath)) + File.Delete(tFileNameNPath); + } + } + + _componentsLoaded.Clear(); + _componentsLoaded = null; + } + + // Indicate that the instance has been disposed. + _disposed = true; + } + } + + #endregion + } +} diff --git a/@integrate/Component.All/Component_Setup_Manager.cs b/@integrate/Component.All/Component_Setup_Manager.cs new file mode 100644 index 0000000..3ae9ab6 --- /dev/null +++ b/@integrate/Component.All/Component_Setup_Manager.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using BridgeConnector.Lib.Assembly; + +namespace Component.All +{ + /// + /// Responsible for working with setup components which don't require any special treatment + /// + public class Component_Setup_Manager : IDisposable, IManageComponents + { + #region Private Members + + private bool _disposed = false; + + private Dictionary _componentsLoaded = new Dictionary(); + + #endregion + + #region Construction + + /// + /// Constructor + /// + public Component_Setup_Manager() + { + } + + /// + /// Finalizer + /// + ~Component_Setup_Manager() + { + Dispose(true); + } + + #endregion + + #region IManageComponents Members + + /// + /// Checks if there are Newer Components embedded then that were installed on the system + /// + /// true, if newer components were found, false otherwise + public bool AreNewerComponentsAvailable() + { + //# If nothing is installed, no need to continue + if (GetAllInstalledComponents() == null) + return true; + + // if the lengths don't match, something must get installed + if (GetAllInstalledComponents().Length != GetAllEmbeddedComponents().Length) + return true; + + // # Otherwise, let's determine 1 by 1 + foreach (ComponentConfig.Component component in GetAllEmbeddedComponents()) + { + int nIndex = Common.GetIndexForComponentUniqueLabel(component.UniqueLabel, GetAllInstalledComponents()); + if (nIndex == -1) + return true; + else if (GetAllInstalledComponents()[nIndex].CompareTo(component) != 0) + return true; + } + return false; + } + + /// + /// Returns the Component Definitions for all Newer components found on the system + /// + /// a list of newer setup components, or empty list for none + public IInstallComponent[] GetNewerComponents(ref SetupEvents setupEventObj) + { + // # Extract all, or let's determine 1 by 1 and extract + bool bInstalledCompsFound = (GetAllInstalledComponents() != null); + foreach (ComponentConfig.Component component in GetAllEmbeddedComponents()) + { + bool bInstall = true; + if (bInstalledCompsFound) + { + int nIndex = Common.GetIndexForComponentUniqueLabel(component.UniqueLabel, GetAllInstalledComponents()); + if (nIndex != -1) + bInstall = (GetAllInstalledComponents()[nIndex].CompareTo(component) != 0); + } + + // mark component for Installation + if (bInstall) + { + string asmName = "Component.Setup." + component.UniqueLabel; + _componentsLoaded[component.UniqueLabel] = Assembly.Load(asmName); + } + } + + List ComponentsToInstall = new List(); + if (_componentsLoaded.Count > 0) + { + foreach (Assembly asm in _componentsLoaded.Values) + { + IInstallComponent installComp = null; + Type[] types = asm.GetTypes(); + foreach (Type t in types) + { + if (t.GetInterface(typeof(IInstallComponent).Name) != null) + { + installComp = (IInstallComponent)asm.CreateInstance(t.FullName); + break; + } + } + + // Check IInstallComponent was found + if (installComp == null) + { + Common.Log.Error(String.Format("Component {0} contains no IInstallComponent Definition.", AssemblyW.GetAssemblyName(asm))); + continue; + } + + // Check if class also implements ISetup + if (installComp is ISetup) + ((ISetup)installComp).ComponentLoaded(ref setupEventObj); + + // Add to Install Components + ComponentsToInstall.Add(installComp); + } + } + return ComponentsToInstall.ToArray(); + } + + /// + /// Retrieves the list of all installed components, in order to uninstall + /// + /// a list of all installed components, null otherwise + public IInstallComponent[] GetAllInstalledComponents(string ComponentsSeperatedbySemiColonOrBlankForAll, ref SetupEvents setupEventObj) + { + // TO DO: + + //string[] ComponentsUniqueLabels = null; + //if (!String.IsNullOrEmpty(ComponentsSeperatedbySemiColonOrBlankForAll)) + // ComponentsUniqueLabels = ComponentsSeperatedbySemiColonOrBlankForAll.Split(';'); + return null; + } + + /// + /// Retrieves the list of all installed components + /// + /// a list of all installed components, null otherwise + public ComponentConfig.Component[] GetAllInstalledComponents() + { + if (Common.InstalledConfig != null && Common.InstalledConfig.SetupComponents.Components.Length > 0) + return Common.InstalledConfig.SetupComponents.Components; + return null; + } + + /// + /// Retrieves the list of all installed componentsW + /// + /// a list of all installed componentsW, null otherwise + public ComponentConfig.ComponentW GetAllInstalledComponentsW() + { + return Common.InstalledConfig.SetupComponents; + } + + /// + /// Retrieves the list of all Embedded components + /// + /// a list of all embedded components, null otherwise + public ComponentConfig.Component[] GetAllEmbeddedComponents() + { + if (Common.EmbeddedConfig != null && Common.EmbeddedConfig.SetupComponents.Components.Length > 0) + return Common.EmbeddedConfig.SetupComponents.Components; + return null; + } + + #endregion + + #region IDisposable Members + + /// + /// Dispose of all extracted files + /// + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer + GC.SuppressFinalize(this); + } + + /// + /// Make sure to conserve disk space, to delete all files that were extracted + /// by this program + /// + /// if true, delete all files extracted by this dll + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _componentsLoaded.Clear(); + _componentsLoaded = null; + } + + // Indicate that the instance has been disposed. + _disposed = true; + } + } + + #endregion + } +} diff --git a/@integrate/Component.All/EmbeddedComponentConfig.xml b/@integrate/Component.All/EmbeddedComponentConfig.xml new file mode 100644 index 0000000..2dceded --- /dev/null +++ b/@integrate/Component.All/EmbeddedComponentConfig.xml @@ -0,0 +1,13 @@ + + + + jre-6u27-windows-i586-s.exe + postgresql-9.1.1-1-windows.exe + pgadmin3-1.14.0.msi + Devart.Data.dll;Devart.Data.PostgreSql.dll;Devart.Data.PostgreSql.xml;gacutil.exe;gacutil.exe.config + mirthconnect-2.1.1.5490.b781-windows.exe + + + BridgeService.exe + + \ No newline at end of file diff --git a/@integrate/Component.All/GenericInstaller.cs b/@integrate/Component.All/GenericInstaller.cs new file mode 100644 index 0000000..65ec7ad --- /dev/null +++ b/@integrate/Component.All/GenericInstaller.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using BridgeConnector.Lib.Thread; + +namespace Component.All +{ + /// + /// Calls GenericInstaller in order to perform an install/uninstall + /// * Does so in a threaded fashion * + /// + public class GenericInstall + { + // Let the Caller know that a) the thread Completed b) it did so successfully + public static bool s_PerformInstallCompleted = false; + public static bool s_PerformInstallCompletedSuccessfully = false; + + // Let the Caller know that a) the thread Completed b) it did so successfully + public static bool s_PerformUninstallCompleted = false; + public static bool s_PerformUninstallCompletedSuccessfully = false; + + // Private ComponentMgmtObjects + private static GenericInstaller s_binaryInstaller = null; + private static GenericInstaller s_setupInstaller = null; + private static SetupEvents s_SetupEvents = new SetupEvents(); + + /// + /// Perform a Threaded Install + /// + public static void PerformInstall() + { + TStarter.ThreadMethod tMethod = delegate() + { + s_PerformInstallCompleted = false; + s_PerformInstallCompletedSuccessfully = false; + bool bBinaryInstallNeeded = false; + bool bSetupInstallNeeded = false; + + // # Let's see if we need to update/install Binary Components + if(s_binaryInstaller == null) + s_binaryInstaller = new GenericInstaller(); + bBinaryInstallNeeded = s_binaryInstaller.IsInstallNeeded(ref s_SetupEvents); + + // # Let's see if we need to update/install Setup Components + if(s_setupInstaller == null) + s_setupInstaller = new GenericInstaller(); + bSetupInstallNeeded = s_setupInstaller.IsInstallNeeded(ref s_SetupEvents); + + // # Let's Install, Baby... + if (bBinaryInstallNeeded || bSetupInstallNeeded) + { + // # Trigger Start Install Event + s_SetupEvents.Raise_Before_Install_Event(); + + bool bBinarySuccess = true; + bool bSetupbSuccess = true; + + // update/install Binary Components + if (bBinaryInstallNeeded) + bBinarySuccess = s_binaryInstaller.Install(); + + // update/install Setup Components + if (bSetupInstallNeeded) + bSetupbSuccess = s_setupInstaller.Install(); + + // # Trigger End Install Event + if (bBinaryInstallNeeded || bSetupInstallNeeded) + s_SetupEvents.Raise_After_Install_Event(); + + // # We fail if one of them fails + s_PerformInstallCompletedSuccessfully = bBinarySuccess && bSetupbSuccess; + } + + // # Let External Callers know that this Thread Completed + s_PerformInstallCompleted = true; + }; + TStarter.StartThread(tMethod, "PerformInstall", System.Threading.ApartmentState.MTA, false, System.Threading.ThreadPriority.Normal); + } + + /// + /// Perform a Threaded Uninstall + /// + public static void PerformUninstall(string ComponentsSeperatedbySemiColonOrBlankForAll) + { + s_PerformUninstallCompleted = false; + s_PerformUninstallCompletedSuccessfully = false; + } + + /// + /// Call this function to let every object know that either PerformInstall or PerformUninstall was called, + /// the Gui then may have made some other modifications, and we are now completed + /// + public static void SetupMainCompleted() + { + // Notify all components that Setup is now complete + s_SetupEvents.Raise_Ending_Setup_Event(); + + // # Imp! - We must call dispose on our Setup Objects + if (s_binaryInstaller != null) + { + s_binaryInstaller.Dispose(); + s_binaryInstaller = null; + } + if (s_setupInstaller != null) + { + s_setupInstaller.Dispose(); + s_setupInstaller = null; + } + } + } + + /// + /// Generic Install Class used for IManageComponents + /// + /// IManageComponents responsible for Installing + public class GenericInstaller : IDisposable where T : IManageComponents, IDisposable, new() + { + #region Private Members + + private T _ComponentMGR = default(T); + private IInstallComponent[] _ComponentsToPerformWorkOn = null; + private bool _disposed = false; + + #endregion + + #region Construction + + /// + /// Construct the T Object + /// + public GenericInstaller() + { + _ComponentMGR = new T(); + } + + /// + /// Finalizer + /// + ~GenericInstaller() + { + Dispose(true); + } + + #endregion + + #region Public Methods + + /// + /// + /// + /// + /// + public bool IsInstallNeeded(ref SetupEvents setupEventObj) + { + if (_ComponentMGR.AreNewerComponentsAvailable()) + { + Common.Log.Info(String.Format("Newer Components available for Type {0}.", _ComponentMGR.GetType().Name)); + _ComponentsToPerformWorkOn = _ComponentMGR.GetNewerComponents(ref setupEventObj); + if (_ComponentsToPerformWorkOn != null && _ComponentsToPerformWorkOn.Length > 0) + { + Common.Log.Info(String.Format("Found {0} Newer Components.", _ComponentsToPerformWorkOn.Length)); + return true; + } + } + + Common.Log.Info(String.Format("No Newer Components available for Type {0}.", _ComponentMGR.GetType().Name)); + return false; + } + + /// + /// Perform Install on the IManageComponents specified when Constructing the GenericInstaller Object + /// + /// true if no error occurs, false otherwise + public bool Install() + { + bool bErrorsOccured = false; + List successfullyInstalledComponents = new List(); + if (_ComponentsToPerformWorkOn != null && _ComponentsToPerformWorkOn.Length > 0) + { + // # Start Install + foreach (IInstallComponent installComp in _ComponentsToPerformWorkOn) + { + ComponentConfig.Component component = installComp.GetComponent(); + if (installComp.BEFORE_INSTALLING_COMPONENT()) + { + Common.Log.Info(String.Format("Installing Component {0} with Version {1}.", component.UniqueLabel, component.Version)); + bool bInstallSuccess = installComp.INSTALL_COMPONENT(); + bInstallSuccess = bInstallSuccess && installComp.AFTER_INSTALLING_COMPONENT(); + if (bInstallSuccess) + { + Common.Log.Info(String.Format("Component {0} with version {1} was correctly installed.", component.UniqueLabel, component.Version)); + successfullyInstalledComponents.Add(component); + } + else + { + bErrorsOccured = true; + string msg = String.Format("Component {0} with version {1} was not correctly installed.", component.UniqueLabel, component.Version); + Common.Log.Error(msg); + } + } + } + // # End Install + + // Add any installed components to the Installed Configuration + foreach (ComponentConfig.Component component in successfullyInstalledComponents) + _ComponentMGR.GetAllInstalledComponentsW().AddUpdateComponent(component.UniqueLabel, component.Version, component.FileName); + + // Write out the installed Configuration + Common.WriteOutChangesToInstalledConfig(); + } + + Common.Log.Info(String.Format("Exiting Install() for Type {0} with bErrorsOccured set to = {1}", _ComponentMGR.GetType().Name, bErrorsOccured)); + return !bErrorsOccured; + } + + /// + /// + /// + /// + /// + public bool IsUninstallNeeded(ref SetupEvents setupEventObj, string ComponentsSeperatedbySemiColonOrBlankForAll) + { + if(_ComponentMGR.GetAllInstalledComponents() != null) + { + Common.Log.Info(String.Format("Installed Components available for Type {0}.", _ComponentMGR.GetType().Name)); + _ComponentsToPerformWorkOn = _ComponentMGR.GetAllInstalledComponents(ComponentsSeperatedbySemiColonOrBlankForAll, ref setupEventObj); + if (_ComponentsToPerformWorkOn != null && _ComponentsToPerformWorkOn.Length > 0) + { + Common.Log.Info(String.Format("Found {0} Components to Uninstall.", _ComponentsToPerformWorkOn.Length)); + return true; + } + } + + Common.Log.Info(String.Format("No Installed Components to Uninstall for Type {0}.", _ComponentMGR.GetType().Name)); + return false; + } + + /// + /// Perform Uninstall on the IManageComponents specified when Constructing the GenericInstaller Object + /// + /// coma-seperated Unique Labels of components to uninstall, blank for all + /// true if no error occurs, false otherwise + public bool Uninstall(ref SetupEvents setupEventObj, string ComponentsSeperatedbySemiColonOrBlankForAll) + { + bool bErrorsOccured = false; + List successfullyUninstalledComponents = new List(); + if (_ComponentsToPerformWorkOn != null && _ComponentsToPerformWorkOn.Length > 0) + { + // # Start Uninstall + foreach (IInstallComponent uninstallComp in _ComponentsToPerformWorkOn) + { + if (!uninstallComp.SUPPORTS_UNINSTALL()) // Not all components support uninstall * although they should, we should allow for this contigency + continue; + + ComponentConfig.Component component = uninstallComp.GetComponent(); + if (uninstallComp.BEFORE_UNINSTALLING_COMPONENT()) + { + Common.Log.Info(String.Format("About to Uninstall Component {0} with Version {1}.", component.UniqueLabel, component.Version)); + bool bUninstallSuccess = uninstallComp.UNINSTALL_COMPONENT(); + if (!bUninstallSuccess) + bErrorsOccured = true; + + bUninstallSuccess = bUninstallSuccess && uninstallComp.AFTER_UNINSTALLING_COMPONENT(); + if (bUninstallSuccess) + { + Common.Log.Info(String.Format("Component {0} with version {1} was uninstalled.", component.UniqueLabel, component.Version)); + successfullyUninstalledComponents.Add(component); + } + else + { + string msg = String.Format("Component {0} with version {1} was not uninstalled.", component.UniqueLabel, component.Version); + Common.Log.Error(msg); + } + } + } + // # End Uninstall + + // Remove any uninstalled components from the Installed Configuration + foreach (ComponentConfig.Component component in successfullyUninstalledComponents) + _ComponentMGR.GetAllInstalledComponentsW().RemoveComponent(component.UniqueLabel); + + // Write out the installed Configuration + Common.WriteOutChangesToInstalledConfig(); + } + + Common.Log.Info(String.Format("Exiting Uninstall() for Type {0} with bErrorsOccured set to = {1}", _ComponentMGR.GetType().Name, bErrorsOccured)); + return !bErrorsOccured; + } + + #endregion + + #region IDisposable Members + + /// + /// Dispose of all extracted files + /// + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer + GC.SuppressFinalize(this); + } + + /// + /// Make sure to call Dispose on the ComponentMGR for it to perform any cleanup + /// + /// if true, dispose ComponentMGR + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _ComponentMGR.Dispose(); + _ComponentMGR = default(T); + } + + // Indicate that the instance has been disposed. + _disposed = true; + } + } + + #endregion + } +} diff --git a/@integrate/Component.All/IInstallComponent.cs b/@integrate/Component.All/IInstallComponent.cs new file mode 100644 index 0000000..689aa24 --- /dev/null +++ b/@integrate/Component.All/IInstallComponent.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Component.All +{ + /// + /// All Components that install themselves must support this interface + /// + public interface IInstallComponent + { + /// + /// Get the Corresponding Component for this IInstallComponent + /// + /// the component for this IInstallCompoent + ComponentConfig.Component GetComponent(); + + /// + /// Used to determine if the component is needed to install + /// + /// true to continue to Install, false otherwise + bool BEFORE_INSTALLING_COMPONENT(); + + /// + /// Used to install a component + /// + /// true, if install was successful, false otherwise + bool INSTALL_COMPONENT(); + + /// + /// Used to do any validation after install occured + /// + /// true, if validation was successful, false otherwise + bool AFTER_INSTALLING_COMPONENT(); + + /// + /// Used to determine if the component supports uninstalling + /// + /// true, if component supports uninstalling, false otherwise + bool SUPPORTS_UNINSTALL(); + + /// + /// Used to determine if the component is needed to uninstall + /// + /// true to continue to uninstall, false otherwise + bool BEFORE_UNINSTALLING_COMPONENT(); + + /// + /// Used to uninstall a component + /// + /// true, if uninstall was successful, false otherwise + bool UNINSTALL_COMPONENT(); + + /// + /// Used to do any validation after uninstall occured + /// + /// true, if validation was successful, false otherwise + bool AFTER_UNINSTALLING_COMPONENT(); + } +} diff --git a/@integrate/Component.All/IManageComponents.cs b/@integrate/Component.All/IManageComponents.cs new file mode 100644 index 0000000..e8b57dc --- /dev/null +++ b/@integrate/Component.All/IManageComponents.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Component.All +{ + /// + /// Used by the Binary and Setup Component Manager, + /// could possible be reused later for a new Component Manager + /// + public interface IManageComponents + { + /// + /// Quick Check to see if newer Components are available + /// + /// true, if newer components are available, false otherwise + bool AreNewerComponentsAvailable(); + + /// + /// Retrieves the list of newer components that need to be installed + /// + /// Setup Event Object is passed, for component manager to pass down to it's components + /// a list of newer components to install, null otherwise + IInstallComponent[] GetNewerComponents(ref SetupEvents setupEventObj); + + /// + /// Retrieves the list of all installed components, in order to uninstall + /// + /// Specify Component Filter + /// Setup Event Object is passed, for component manager to pass down to it's components + /// a list of all installed components, null otherwise + IInstallComponent[] GetAllInstalledComponents(string ComponentsSeperatedbySemiColonOrBlankForAll, ref SetupEvents setupEventObj); + + /// + /// Retrieves the list of all installed components + /// + /// a list of all installed components, null otherwise + ComponentConfig.Component[] GetAllInstalledComponents(); + + /// + /// Retrieves the list of all installed componentsW + /// + /// a list of all installed componentsW, null otherwise + ComponentConfig.ComponentW GetAllInstalledComponentsW(); + + /// + /// Retrieves the list of all Embedded components + /// + /// a list of all embedded components, null otherwise + ComponentConfig.Component[] GetAllEmbeddedComponents(); + } +} diff --git a/@integrate/Component.All/ISetup.cs b/@integrate/Component.All/ISetup.cs new file mode 100644 index 0000000..e9c86bc --- /dev/null +++ b/@integrate/Component.All/ISetup.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Component.All +{ + /// + /// Class is passed into ISetup ComponentLoaded() giving each component + /// a chance to subscribe to it + /// + public class SetupEvents + { + #region Events + + /// + /// Delegate used to handle Setup Events + /// + public delegate void SetupEvent(); + + /// + /// Called after the entire Setup Application ends + /// + public event SetupEvent Ending_Setup; + + /// + /// Called before the entire Install Process Begins + /// + public event SetupEvent Before_Install; + + /// + /// Called after the entire Install Process ends + /// + public event SetupEvent After_Install; + + /// + /// Called before the entire Uninstall Process Begins + /// + public event SetupEvent Before_Uninstall; + + /// + /// Called after the entire Uninstall Process ends + /// + public event SetupEvent After_Uninstall; + + #endregion + + # region Event Raisers + + /// + /// Raise the Ending Setup Event + /// + public void Raise_Ending_Setup_Event() + { + if (Ending_Setup != null) + Ending_Setup(); + } + + /// + /// Raise the Before Install Event + /// + public void Raise_Before_Install_Event() + { + if (Before_Install != null) + Before_Install(); + } + + /// + /// Raise the After Install Event + /// + public void Raise_After_Install_Event() + { + if (After_Install != null) + After_Install(); + } + + /// + /// Raise the Before Uninstall Event + /// + public void Raise_Before_Uninstall_Event() + { + if (Before_Uninstall != null) + Before_Uninstall(); + } + + /// + /// Raise the After Uninstall Event + /// + public void Raise_After_Uninstall_Event() + { + if (After_Uninstall != null) + After_Uninstall(); + } + + #endregion + } + + /// + /// All Components that install themselves will implement this interface, if they are interested to + /// listen to / Handle Setup Events + /// + public interface ISetup + { + /// + /// Called when the Component is loaded. Components are only loaded + /// when an Install on them is iminent. The component has a chance to + /// listen to/handle events in the setup process. + /// + void ComponentLoaded(ref SetupEvents subscribeToDesiredEvents); + } + +} diff --git a/@integrate/Component.All/Properties/AssemblyInfo.cs b/@integrate/Component.All/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..580d301 --- /dev/null +++ b/@integrate/Component.All/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Components")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Components")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ae8a648a-90cf-48ce-9b71-6e6b4cc417aa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/@integrate/HiddenMainWindow.xaml b/@integrate/HiddenMainWindow.xaml new file mode 100644 index 0000000..270af8b --- /dev/null +++ b/@integrate/HiddenMainWindow.xaml @@ -0,0 +1,7 @@ + + + + diff --git a/@integrate/HiddenMainWindow.xaml.cs b/@integrate/HiddenMainWindow.xaml.cs new file mode 100644 index 0000000..b099576 --- /dev/null +++ b/@integrate/HiddenMainWindow.xaml.cs @@ -0,0 +1,1456 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using WatchdogLib.WPF; +using WatchdogLib.Thread; +using System.Timers; +using Watchdog.WatchdogLib.Process; +using System.Diagnostics; +using WatchdogLib.File; +using WatchdogLib.Tools; +using Watchdog.WatchdogLib.File; +using Watchdog.WatchdogLib.Registry; +using Watchdog.WatchdogLib.Assembly; +using System.Windows.Threading; +using Watchdog.WatchdogLib.Other; +using Watchdog.WPFWindows; +using Watchdog.WatchdogLib.Monitor; +using Watchdog.WatchdogLib.Net; +using Watchdog.WatchdogLib.Win32; + +namespace Watchdog +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class HiddenMainWindow : Window + { + #region Private Statics + + // Core Timers * And HiddenWindow Var * + private static KeepHidden _HiddenWindow = null; + private static TTimer _timer = new TTimer(TTimer_ElapsedEventHandler, 1523, false, false); // Kicks in every ~1.5 Seconds + private static TTimer _timerScheduler = new TTimer(TTimer_ElapsedEventHandlerScheduler, 61491, false, false); // Kicks in every ~1 Minute + private static TTimer _timerSchedulerBackup = new TTimer(TTimer_ElapsedEventHandlerSchedulerBackup, 99986, false, false); // Kicks in every 1.665 Minutes (uneven so timers have the highest chance of not overlapping) + + // In case an unexpected process shutdown occurs, let's try to do more clean-up + private static bool s_WindowCloseGotCalled = false; + + // to makes sure we have a clean shut down * Prev State Tracking * + private static bool s_bIsTimerRunning = false; + private static bool s_bIsSchedulerTimerRunning = false; + private static bool s_bIsSchedulerBackupTimerRunning = false; + + // Start/Stop Timers *While shuting down _timer can be null* calling These functions and ignore any errors, + // Functions return the * previous state before calling * so if the previous state was 'Off' then it returns false, previous state was 'On' it returns true; + private static bool StartTimer() { lock (_HiddenWindow) { bool bPrevState = s_bIsTimerRunning; try { _timer.Start(); s_bIsTimerRunning = true; } catch (Exception) { /* ignore */ } return bPrevState; } } + private static bool StopTimer() { lock (_HiddenWindow) { bool bPrevState = s_bIsTimerRunning; try { _timer.Stop(); s_bIsTimerRunning = false; } catch (Exception) { /* ignore */ } return bPrevState; } } + private static bool StartSchedulerTimer() { lock (_HiddenWindow) { bool bPrevState = s_bIsSchedulerTimerRunning; try { _timerScheduler.Start(); s_bIsSchedulerTimerRunning = true; } catch (Exception) { /* ignore */ } return bPrevState; } } + private static bool StopSchedulerTimer() { lock (_HiddenWindow) { bool bPrevState = s_bIsSchedulerTimerRunning; try { _timerScheduler.Stop(); s_bIsSchedulerTimerRunning = false; } catch (Exception) { /* ignore */ } return bPrevState; } } + //private static bool StartSchedulerBackupTimer(uint IntervalInMiliseconds = 99986) { lock (_HiddenWindow) { bool bPrevState = s_bIsSchedulerBackupTimerRunning; try { _timerSchedulerBackup.Start(IntervalInMiliseconds); s_bIsSchedulerBackupTimerRunning = true; } catch (Exception) { /* ignore */ } return bPrevState; } } + private static bool StartSchedulerBackupTimer(uint IntervalInMiliseconds) { lock (_HiddenWindow) { bool bPrevState = s_bIsSchedulerBackupTimerRunning; try { _timerSchedulerBackup.Start(IntervalInMiliseconds); s_bIsSchedulerBackupTimerRunning = true; } catch (Exception) { /* ignore */ } return bPrevState; } } + private static bool StopSchedulerBackupTimer() { lock (_HiddenWindow) { bool bPrevState = s_bIsSchedulerBackupTimerRunning; try { _timerSchedulerBackup.Stop(); s_bIsSchedulerBackupTimerRunning = false; } catch (Exception) { /* ignore */ } return bPrevState; } } + + #endregion + + #region Internal Statics * (Monitor API) * + + /// + /// Use this to Start the Process Monitor * Also Called by Scheduler and Scheduler Backup * + /// + /// true to force a one-time refresh of XML DS Data, false otherwise + /// true to interact with desktop, false otherwise + //internal static void StartMonitoring(bool bReloadConfigurationData = false, bool bShowUserPopup = true) + internal static void StartMonitoring(bool bReloadConfigurationData, bool bShowUserPopup) + { + // If the Monitor was Paused Prior? - restart the Timers + if (App.State_IsMonitorPaused()) + { + lock (_HiddenWindow) + { + // Set to Running + App.State_MonitorStarted(); + + // Re-start Timers + StartTimer(); + StartScheduler(); + StartSchedulerBackup(99986); + + // Log and Inform the User + App.log.Info(AppResx.GetStringFormated("MESSAGE_MONITOR_STARTED_FROM_PAUSE", App.APPLICATION_NAME_SHORT, false)); + if(bShowUserPopup && !App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_START_SUCCESS", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME)); + + // * Set the System Tray Icon According to Running State * + if(!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.SetSysTrayIconAccordingToRunningState(App.State_GetSystemState()); + return; + } + } + + // Is the Monitor already running? + if (App.State_MonitorIsInRunningState()) + return; + + // Set to Running + App.State_MonitorStarted(); + + // Let's get Started + lock (_HiddenWindow) + { + // Reset the Last Scheduler Run Time + if (!App.State_IsSchedulerExecuting()) + App.setting.LastSchedulerRun = DateTime.MinValue; + + // Reset the Last Scheduler Backup Run Time + if (!App.State_IsSchedulerBackupExecuting()) + App.setting.LastSchedulerBackupRun = DateTime.MinValue; + + // Reset Error State * But Only if we are NOT being Called by the Scheduler * + if (!App.State_IsSchedulerExecuting()) + App.State_Error_ResetErrorState(); + + // Log Start of monitoring + App.log.Info(AppResx.GetStringFormated("MESSAGE_MONITOR_STARTED", App.APPLICATION_NAME_SHORT, bReloadConfigurationData ? "True" : "False")); + + // * Set the System Tray Icon According to Running State * + if(!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.SetSysTrayIconAccordingToRunningState(App.State_GetSystemState()); + + // Popup Std Start Monitoring Message + if (bShowUserPopup && !App.State_SpecialMode_IsRunAsServiceSet()) + { + if (App.State_IsSchedulerExecuting()) + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_STARTSTOP", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME)); + else if (App.State_IsSchedulerBackupExecuting()) + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_BACKUP_STARTSTOP_DISPLAY2", App.APPLICATION_NAME_SHORT)); + else + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_START_SUCCESS", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME)); + } + + // Reload Configuration, if neccessary + App.xmlfile.ForceRefreshOnNext_ReadData = bReloadConfigurationData; + if (!bReloadConfigurationData) + { + // make sure that at least all the process/service info is cleared + App.xmlfile.ReadData(false).ClearAllStartNFailTimes(); + // also clear PID information + App.xmlfile.ReadData(false).MonitoredProcesses.ClearPidInformation(); + } + + // Let the Schedulers Handle the starting of the Timers, + // * don't start anything if it is already executing - concurrency * + if (!App.State_IsSchedulerExecuting() && !App.State_IsSchedulerBackupExecuting()) + { + if(App.setting.SchedulerSettingsExist || App.setting.SchedulerBackupSettingsExist) + { + // When we StartMonitoring, we should delay start the Scheduler, + // to give the timer a change to start all processes. 10 Seconds should do. + ElapsedEventHandler timedStart = delegate(object sender, ElapsedEventArgs e) + { + Timer timer = (Timer)sender; + timer.Stop(); + StartScheduler(); // * Start the Scheduler, if it is set * + StartSchedulerBackup(99986); // * Start the Scheduler Backup, if it is set * + timer.Dispose(); + timer = null; + }; + TTimer delayStartScheduler = new TTimer(timedStart, 5000, true, true); + } + } + + // Allow Scheduler to manage this timer + if(!App.State_IsSchedulerExecuting()) + StartTimer(); + } + } + + /// + /// Pause the Monitor * Also Called by Scheduler Backup * + /// + /// true to interact with desktop, false otherwise + //internal static void PauseMonitoring(bool bShowUserPopup = true) + internal static void PauseMonitoring(bool bShowUserPopup) + { + if (App.State_IsMonitorPaused() || !App.State_MonitorIsInRunningState()) + return; + + App.log.Info("PauseMonitoring Called"); + + lock (_HiddenWindow) + { + // Stop Both Timers, inside a Lock, + // should do the trick * Neither should execute * + StopTimer(); + StopScheduler(); + if (!App.State_IsSchedulerBackupExecuting()) + StopSchedulerBackup(); + + // Mark the System as a Paused State + App.State_MonitorPaused(); + + // Log Pause of monitoring + App.log.Info(AppResx.GetStringFormated("MESSAGE_MONITOR_PAUSED", App.APPLICATION_NAME_SHORT)); + if (bShowUserPopup && !App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_MONITOR_PAUSED", App.APPLICATION_NAME_SHORT)); + + // * Set the System Tray Icon According to Current State * + if(!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.SetSysTrayIconAccordingToRunningState(App.State_GetSystemState()); + } + } + + /// + /// Restarts the Monitor * Also Called by Scheduler * + /// + /// Time in seconds to wait before starting (after stopping) + /// true to interact with desktop, false otherwise + //internal static void RestartMonitoring(uint nWaitBeforeStartSeconds = 4, bool bShowUserPopup = true) + internal static void RestartMonitoring(uint nWaitBeforeStartSeconds, bool bShowUserPopup) + { + App.log.Info("RestartMonitoring Called"); + + // Stop the Monitor + StopMonitoring(bShowUserPopup, false); + + // Sleep, for a little while,... + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(nWaitBeforeStartSeconds)); + + // Now Start again... + StartMonitoring(!App.State_IsSchedulerExecuting(), bShowUserPopup); + } + + /// + /// Restarts the Monitor, including all services + /// + /// Time in seconds to wait before starting (after stopping) + /// true to interact with desktop, false otherwise + //internal static void RestartAllMonitoring(uint nWaitBeforeStartSeconds = 4, bool bShowUserPopup = true) + internal static void RestartAllMonitoring(uint nWaitBeforeStartSeconds, bool bShowUserPopup) + { + App.log.Info("RestartAllMonitoring Called"); + + // Stop the Monitor + StopMonitoring(bShowUserPopup, true); + + // Sleep, for a little while,... + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(nWaitBeforeStartSeconds)); + + // Now Start again... + StartMonitoring(!App.State_IsSchedulerExecuting(), bShowUserPopup); + } + + /// + /// Stops Monitoring, including all Services + /// + /// true to interact with desktop, false otherwise + //internal static void StopAllMonitoring(bool bShowUserPopup = true) + internal static void StopAllMonitoring(bool bShowUserPopup) + { + App.log.Info("StopAllMonitoring Called"); + StopMonitoring(bShowUserPopup, true); + } + + /// + /// Use this to Stop the Process Monitor * Also Called by Scheduler and Scheduler Backup * + /// + //internal static void StopMonitoring(bool bShowUserPopup = true, bool bForceShutdownOfServices = false) + internal static void StopMonitoring(bool bShowUserPopup, bool bForceShutdownOfServices) + { + // Is the Monitor even running? + if (!App.State_MonitorIsInRunningState()) + return; + + // Set to Stopped + App.State_MonitorStopped(); + + lock (_HiddenWindow) + { + // Reset Error State * But Only if we are NOT being Called by the Scheduler * + if (!App.State_IsSchedulerExecuting() && !App.State_IsSchedulerBackupExecuting()) + App.State_Error_ResetErrorState(); + + // Log Stop of monitoring + App.log.Info(AppResx.GetStringFormated("MESSAGE_MONITOR_STOPPED", App.APPLICATION_NAME_SHORT)); + + // Stop the Internal Timers * Always do this * just in case + StopTimer(); + StopSchedulerTimer(); + if (!App.State_IsSchedulerBackupExecuting()) + StopSchedulerBackupTimer(); + + // Read the config + WatchdogConfiguration config = App.xmlfile.ReadData(false); + + // * Set the System Tray Icon According to Running State * + if(!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.SetSysTrayIconAccordingToRunningState(App.State_GetSystemState()); + + // Popup Std Stop Monitoring Message * But Only if Scheduler && Scheduler Backup is NOT running * + if (bShowUserPopup && !App.State_SpecialMode_IsRunAsServiceSet()) + { + if(App.State_IsSchedulerExecuting()) + { + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_STARTSTOP_DISPLAY1", App.APPLICATION_NAME_SHORT)); + App.log.Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_STARTSTOP_DISPLAY1", App.APPLICATION_NAME_SHORT)); + } + else if (App.State_IsSchedulerBackupExecuting()) + { + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_BACKUP_STARTSTOP_DISPLAY1", App.APPLICATION_NAME_SHORT)); + App.log.Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_BACKUP_STARTSTOP_DISPLAY1", App.APPLICATION_NAME_SHORT)); + } + else + { + if (config.ConfiguredHasAny) + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_STOP_SUCCESS", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME, bForceShutdownOfServices)); + else + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_NO_CONFIGURATION_FOUND", App.APP_XML_DS_FILENAME)); + } + + if (config.ConfiguredHasAny) + App.log.Info(AppResx.GetStringFormated("MESSAGE_STOP_SUCCESS", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME, bForceShutdownOfServices)); + else + App.log.Error(AppResx.GetStringFormated("MESSAGE_NO_CONFIGURATION_FOUND", App.APP_XML_DS_FILENAME)); + } + + // Shutdown any of our Running Processes + TraceM.TraceBegin(); + bool bKillApplications = !(App.State_SpecialCircum_ShouldWeNotCloseApplicationsOnExit() && s_WindowCloseGotCalled); + if(bKillApplications) + ProcessKillAllMonitoredProcessesIfAny(config); + TraceM.TraceEnd("Killing Running Applications"); + + // Shutdown any of our Running Services + TraceM.TraceBegin(); + ServicesKillAllMonitoredServicesIfAny(config, bForceShutdownOfServices); + TraceM.TraceEnd("Killing Running Services"); + + // Try Reclaim memory by running the Garbage Collector + // * Really appears to not be doing much * + if (!App.State_IsSchedulerExecuting() && !App.State_IsSchedulerBackupExecuting()) + { + TraceM.TraceBegin(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + TraceM.TraceEnd("Garbage Collector Called"); + } + } + } + + /// + /// Use this to Start the Scheduler + /// + internal static void StartScheduler() + { + lock (_HiddenWindow) + { + // Start the Scheduler Only if there is any Scheduler Information (and the Settings Window + // isn't currently being shown - Settings window disables/enables Scheduler on it's own * + if (!App.State_IsSchedulerExecuting() && App.setting.SchedulerSettingsExist && + !WPFWindowMaker.IsWindowOfTypeWPFWindowShowing_WithinLock(WPFWindow.Settings_Window)) + StartSchedulerTimer(); + } + } + + /// + /// Use this to Stop the Scheduler + /// + internal static void StopScheduler() + { + lock (_HiddenWindow) + { + StopSchedulerTimer(); + } + } + + /// + /// Use this to Start the Scheduler Backup + /// + //internal static void StartSchedulerBackup(uint IntervalInMiliseconds = 99986) + internal static void StartSchedulerBackup(uint IntervalInMiliseconds) + { + lock (_HiddenWindow) + { + // Start the Scheduler Backup Only if there is any Scheduler Backup Information (and the Settings Window + // isn't currently being shown - Settings window disables/enables Scheduler Backup on it's own * + if (!App.State_IsSchedulerBackupExecuting() && App.setting.SchedulerBackupSettingsExist && + !WPFWindowMaker.IsWindowOfTypeWPFWindowShowing_WithinLock(WPFWindow.Settings_Window)) + StartSchedulerBackupTimer(IntervalInMiliseconds); + } + } + + /// + /// Use this to Stop the Scheduler Backup + /// + internal static void StopSchedulerBackup() + { + lock (_HiddenWindow) + { + StopSchedulerBackupTimer(); + } + } + + #endregion + + #region Construction + + /// + /// Constructor + /// + public HiddenMainWindow() + { + InitializeComponent(); + + // Only continue, if no fatal application error occured + if (!App.s_FatalErrorOccured) + { + // * Force this window to remain hidden indefinitly* + _HiddenWindow = new KeepHidden(this); + + // Assign ProcessExit Event to Handle more specialized Cleanup + AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit); + + // We DON'T NEED TO SHOW THE HIDDEN WINDOW * Triggering OnLoad(), if we are + // being launched via the Command-Line. + if (App.State_SpecialMode_IsCommandLineSet()) + { + // If we are in Command-Line Mode * Then Hidden Window should never be shown * + // in fact we'll enter into a whole new entry point * CommandLine MODE + ElapsedEventHandler EnterCommandLineMode = delegate(object sender, ElapsedEventArgs e) + { + Timer timer = (Timer)sender; + timer.Stop(); + + // * Enter CommandLine Mode * + CMDlineHandler.EnterCommandLineMode(); + + timer.Dispose(); + timer = null; + + // Done Here, shut down the app + App.Current.Shutdown(); + }; + TTimer timerEnterCommandLineMode = new TTimer(EnterCommandLineMode, 500, true, true); + } + else + { + // Calling Show() directly in the Constructor causes a .Net Run-time Crash + // upon first launch only. Calling it .5 seconds later does the trick of avoiding that error, + // because by then the object is fully constructed + ElapsedEventHandler DelayShowHiddenWindow = delegate(object sender, ElapsedEventArgs e) + { + Timer timer = (Timer)sender; + timer.Stop(); + + // * Show the Hidden Window * + _HiddenWindow.Show(); + + timer.Dispose(); + timer = null; + }; + TTimer timerDelayShowHiddenWindow = new TTimer(DelayShowHiddenWindow, 500, true, true); + } + } + } + + #endregion + + #region Process Monitor / Service Monitor * CORE LOGIC * + + /// + /// Called every Nth Interval, does the grunt of the work for this whole + /// Application. It checks the processes, and if they are running, etc... + /// * Core Business Logic * + /// + private static void TTimer_ElapsedEventHandler(object sender, ElapsedEventArgs e) + { + lock (_HiddenWindow) + { + // Stop the timer to prevent multiple call-Ins, while work is still being done + StopTimer(); + + // Everything is Good, Monitor the Processes / Services Start any processes / Services that aren't available + if (App.State_MonitorIsInRunningState() && !App.State_IsMonitorPaused()) + { + // Get the Configuration + WatchdogConfiguration config = App.xmlfile.ReadData(false); + + // * NO CONFIGURATION * Stop! - Nothing to monitor + if (!config.ConfiguredHasAny) + { + StopMonitoring(true, false); + return; + } + + // * Read the Ini Configuration Settings * + TraceM.TraceBegin(); + uint nMaxFailureCount = App.setting.MaxFailureCount; + uint nMaxFailureCountHour = App.setting.MaxFailureCountHour; + TraceM.TraceEnd("Monitor - Reading in INI Configuration"); + + // * SERVICE MONITORING * + TraceM.TraceBegin(); + bool bErrorThrownInServicesMonitor = !ServiceMonitor(config, nMaxFailureCount, nMaxFailureCountHour); + TraceM.TraceEnd("* Service Monitoring *"); + + // * PROCESS MONITORING * + TraceM.TraceBegin(); + bool bErrorThrownInProcessMonitor = !ProcessMonitor(config, nMaxFailureCount, nMaxFailureCountHour); + TraceM.TraceEnd("* Process Monitoring *"); + + // If Any Unknown Errors were Thrown! Stop the Monitor + if (bErrorThrownInServicesMonitor || bErrorThrownInProcessMonitor) + { + StopMonitoring(true, false); + + //// + // An Error Occured with the Application * Better Notify User, if specified * + //// + if (App.setting.EmailNotificationEnabled && App.setting.EmailSettingsValid) + { + bool bIsSend = Emailer.SendEmail(App.setting.EmailStmpServer, App.setting.EmailSenderEmail, App.APPLICATION_NAME_SHORT, + App.setting.EmailReceiverEmail, AppResx.GetStringFormated("ERROR_EMAIL_SUBJECT", App.APPLICATION_NAME_SHORT), + AppResx.GetStringFormated("ERROR_EMAIL_BODY", App.APPLICATION_NAME_SHORT, App.setting.EmailCustomMessageText), + App.setting.EmailSmtpPort, String.Empty, App.setting.EmailStmpServerRequiresAuth, App.setting.EmailSmtpUsername, App.setting.EmailSmtpPassword, + App.setting.EmailStmpServerRequiresSSL, 30); + if (!bIsSend) + App.log.Error("Failed to Send ERROR_EMAIL"); + else + App.log.Info(String.Format("Email (ERROR_EMAIL) Notification send to {0}", App.setting.EmailReceiverEmail)); + } + return; + } + + if (!App.State_IsMonitorPaused()) // If the System Is Paused, no need to start the Timer again + { + // Re-Start the timer, if all went well + // * At the end of each Cycle, Set the System Tray Icon Accordingly * Keep track when we were last run + if(!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.SetSysTrayIconAccordingToRunningState(App.State_GetSystemState()); + StartTimer(); // Everything went well, continue... + } + } + } + } + + /// + /// Used by the Scheduler Timer to determine if a restart is to occur + /// + /// now datetime stamp + /// scheduler start datetime stamp + /// datetime when last run occured + /// numeric value indicating hours or minutes + /// true if hours, false if minutes + /// true if we want the Scheduler to Restart, false otherwise + private static bool PerformRestartTimeSchedulerTriggered(DateTime now, DateTime start, DateTime LastRun, uint nHoursOrMinutes, bool bIsHours) + { + // * OLD CALCULATION * OLD SCHOOL * Make sure enough time has passed since last run, otherwise no need to continue + //TimeSpan tsLastRun = now - LastRun; + //bool bEnoughTimeSinceLastRunHasPassed = bIsHours ? (((double)(tsLastRun.TotalMinutes / 60)) >= ((double)nHoursOrMinutes)) : (tsLastRun.TotalMinutes >= ((double)nHoursOrMinutes)); + //return bEnoughTimeSinceLastRunHasPassed; + + // If the current time is within 2 minutes of last run, there is no need to continue + TimeSpan tsDiffBetweenNowAndLastRun = (now - LastRun); + bool bIsWithin2MinutesOfLastRun = (tsDiffBetweenNowAndLastRun.TotalMinutes >= 0) && (tsDiffBetweenNowAndLastRun.TotalMinutes <= 2.9997); + if (bIsWithin2MinutesOfLastRun) + return false; + + // Check the start time * if we are near the start time and a last run hasn't occured, then yes, run this * + TimeSpan tsDiffBetweenNowAndStart = (now - start); + bool bIsWithin2MinutesOfScheduledStartTime = (tsDiffBetweenNowAndStart.TotalMinutes >= 0) && (tsDiffBetweenNowAndStart.TotalMinutes <= 2.9997); + if (bIsWithin2MinutesOfScheduledStartTime) + return true; + + // Perform Cycle Calculation + double durationInMinutes = bIsHours ? nHoursOrMinutes * 60 : nHoursOrMinutes; + double remCycle = tsDiffBetweenNowAndStart.TotalMinutes % durationInMinutes; + bool bIsWithin2MinutesOfRemCycle = (remCycle >= 0) && (remCycle <= 2.9997); + return bIsWithin2MinutesOfRemCycle; + } + + /// + /// Called every Nth Interval, does the grunt of the work for the Scheduler + /// * Core Business Logic * + /// + private static void TTimer_ElapsedEventHandlerScheduler(object sender, ElapsedEventArgs e) + { + lock (_HiddenWindow) + { + // *imp * stop the process/services monitor (so that while + // this timer occurs it won't conflict with what happens here + bool bPrevStateTimer = StopTimer(); + + // Stop the Scheduler timer to prevent multiple call-Ins, while work is still being done + StopSchedulerTimer(); + + // Everything is Good, + if (App.State_MonitorIsInRunningState() && !App.State_IsSchedulerExecuting() && !App.State_IsMonitorPaused()) + { + // Set the Scheduler State + App.State_SchedulerExecution_Started(); + + // Fetch scheduler settings + bool bFoundSchedulerSettings = false; + DateTime dtStart = DateTime.MinValue; + DateTime dtLastRun = DateTime.MinValue; + uint nHoursOrMinutes = 0; + bool bIsHours = true; + + // Only Do something if there are any settings + bFoundSchedulerSettings = App.setting.GetSchedulerSettings(out dtStart, out dtLastRun, out nHoursOrMinutes, out bIsHours); + if (!bFoundSchedulerSettings) + return; // No Scheduler Settings, no need to continue + + // Get the Now Time + DateTime dtNow = DateTime.Now; + + // Make sure we've reached the Start Time, else there is no need to continue + if (dtNow >= dtStart && PerformRestartTimeSchedulerTriggered(dtNow, dtStart, dtLastRun, nHoursOrMinutes, bIsHours)) + { + // Log the fact that Scheduler is about to stop / start + App.log.Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_STARTSTOP", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME)); + + // * Perform Restart * + RestartMonitoring(4, true); + + // Set Last Run + App.setting.LastSchedulerRun = dtNow; + } + + // Reset the Scheduler State + App.State_SchedulerExecution_Stopped(); + } + + if (!App.State_IsMonitorPaused()) // If the System Is Paused, no need to start the Timers again + { + // Re-Start the Scheduler timer * Everything went well s * + StartScheduler(); + + // Now it should be save to start the Main Monitor Timer + if (bPrevStateTimer) + StartTimer(); + } + } + } + + /// + /// Integrity - When we fire a start Trigger with SchedulerBackupTriggerOccured(), in case another call in occurs, + /// we know to ignore it + /// + private static DateTime _LastStartTriggerFired = DateTime.MinValue; + + /// + /// Integrity - When we fire a end Trigger with SchedulerBackupTriggerOccured(), in case another call in occurs, + /// we know to ignore it + /// + private static DateTime _LastEndTriggerFired = DateTime.MinValue; + + /// + /// Deterimes the State of the Scheduler Backup and what action to take + /// + /// The Now DateTime + /// Scheduled Start DateTime + /// Last Run, should be set when the first start occured and passed into here for checking + /// the duration from start to end + /// we want to perform the following action * Trigger Occured *, 0 = None, 1 = Start, 2 = End + private static uint SchedulerBackupTriggerOccured (DateTime dtNow, DateTime dtScheduledStartDT, DateTime dtLastRun, TimeSpan tsDuration) + { + // Time Of Day Vars + TimeSpan TimeOfDayNow = dtNow.TimeOfDay; + TimeSpan TimeOfDayScheduled = dtScheduledStartDT.TimeOfDay; + + // Debug + //dtNow = DateTime.Parse("8/13/2011 01:57:29 AM"); + //TimeOfDayNow = new TimeSpan(1, 57, 29); + //TimeOfDayScheduled = new TimeSpan(1, 56, 11); + //tsDuration = new TimeSpan(4, 0, 0); + //dtLastRun = DateTime.Parse("1/1/0001 12:00:00 AM"); + + // Time of Day Within Time Calc; + TimeSpan tsDiffBetweenNowAndScheduled = (TimeOfDayNow - TimeOfDayScheduled); + bool bIsWithin2MinutesOfScheduledTime = (tsDiffBetweenNowAndScheduled.TotalMinutes > 0) && (tsDiffBetweenNowAndScheduled.TotalMinutes <= 2.9997); + TimeSpan tsDiffBetweenNowAndScheduledDurationEnd = TimeOfDayNow - (TimeOfDayScheduled + tsDuration); + if(tsDiffBetweenNowAndScheduledDurationEnd.TotalMinutes < 0) // A Day must have passed + tsDiffBetweenNowAndScheduledDurationEnd = tsDiffBetweenNowAndScheduledDurationEnd + new TimeSpan(24, 00, 00); + bool bIsWithin2MinutesOfScheduledTimeDurationEnd = (tsDiffBetweenNowAndScheduledDurationEnd.TotalMinutes <= 2.9997); + + // Last Run Calculations * make sure to see if it was started prior + // ~Last Run allows us to determine if there exists a start for an end, + // we only check the end event to see if a start occured + if (bIsWithin2MinutesOfScheduledTimeDurationEnd) + { + TimeSpan tsDiffBetweenNowAndLastRun = dtNow - dtLastRun; + TimeSpan tsDiffInDuration = tsDiffBetweenNowAndLastRun - tsDuration; + + // In the event the Monitor App got started somewhere in between times, + // and we trigger the end event (because time was reached), we + // are checking last run, to see if it coincides with this end event, + // if it doesn't (as in it is way too long ago ~here anything above 10 minutes out + // of bounds from the duration), we won't trigger the end event, assuming + // that it never started in this cycle to begin with, waiting for the next cycle + bool bIsLastRunOutOfBounce = (tsDiffInDuration.TotalMinutes > 0) && (tsDiffInDuration.TotalMinutes >= 10); + if (bIsLastRunOutOfBounce) + return 0; + } + + // Integrity Check * prevent multiple callins within time boundary 2.9997, * or actually + // within the same day * may as well, we only allow 1 start/end trigger per day + if (bIsWithin2MinutesOfScheduledTime) + { + TimeSpan ts = dtNow - _LastStartTriggerFired; + if ((ts.TotalMinutes > 0) && (ts.TotalMinutes > 1000)) // we should only be firing on start trigger per day aka 1440, but 1000 is a good check (even) + _LastStartTriggerFired = dtNow; + else + bIsWithin2MinutesOfScheduledTime = false; + } + else if (bIsWithin2MinutesOfScheduledTimeDurationEnd) + { + TimeSpan ts = dtNow - _LastEndTriggerFired; + if ((ts.TotalMinutes > 0) && (ts.TotalMinutes > 1000)) // we should only be firing on end trigger per day aka 1440, but 1000 is a good check (even) + _LastStartTriggerFired = dtNow; + else + bIsWithin2MinutesOfScheduledTime = false; + } + + // Trigger Start / Stop / Or Nothing + if (bIsWithin2MinutesOfScheduledTime) + return 1; + else if (bIsWithin2MinutesOfScheduledTimeDurationEnd) + return 2; + else + return 0; + } + + /// + /// Called every Nth Interval, does the grunt of the work for the Scheduler Backup + /// * Core Business Logic * + /// + private static void TTimer_ElapsedEventHandlerSchedulerBackup(object sender, ElapsedEventArgs e) + { + lock (_HiddenWindow) + { + // Stop the Scheduler Backup timer to prevent multiple call-Ins, while work is still being done + StopSchedulerBackupTimer(); + + // Everything is Good, + //if (App.State_MonitorIsInRunningState() && !App.State_IsSchedulerBackupExecuting()) // BUG + if (!App.State_IsSchedulerBackupExecuting()) + { + // Set the Scheduler State + App.State_SchedulerBackupExecution_Started(); + + // Fetch scheduler Backup settings + bool bFoundSchedulerBackupSettings = false; + DateTime dtStart = DateTime.MinValue; + DateTime dtLastRun = DateTime.MinValue; + uint nHoursOrMinutes = 0; + bool bIsHours = true; + + // Only Do something if there are any settings + bFoundSchedulerBackupSettings = App.setting.GetSchedulerBackupSettings(out dtStart, out dtLastRun, out nHoursOrMinutes, out bIsHours); + if (!bFoundSchedulerBackupSettings) + return; // No Scheduler Backup Settings, no need to continue + + // Get the Now Time + DateTime dtNow = DateTime.Now; + + // Make sure we've reached the Start Time, else there is no need to continue + if (dtNow >= dtStart) + { + // Get the Duration + TimeSpan tsDuration = bIsHours ? TimeSpan.FromHours(nHoursOrMinutes) : TimeSpan.FromMinutes(nHoursOrMinutes); + + // Determine the State of the Scheduler Backup an the Action To Take + uint nTriggerResult = SchedulerBackupTriggerOccured(dtNow, dtStart, dtLastRun, tsDuration); + + bool bPerformAnAction = (nTriggerResult == 1) || (nTriggerResult == 2); + if (bPerformAnAction) + { + // Get Current State + App.StateSystem state = App.State_GetSystemState(); + bool bStopInsteadOfPause = App.setting.SchedulerBackupStopInsteadOfPause; + bool bStopServices = App.setting.SchedulerBackupStopServices; + + // Log the fact that Scheduler Backup is about to pause / stop / start + App.log.Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_BACKUP_STARTSTOP", App.APPLICATION_NAME_SHORT, (state == App.StateSystem.Paused))); + + // * Perform System Pause * or + // * Perform System Start *, or * System Stop *, depending on system state and Settings + switch (nTriggerResult) + { + case 1: // Duration Start + { + // We are in a wrong state to perform a start + if (state != App.StateSystem.Error && + state != App.StateSystem.Running) + { + App.log.Error(String.Format("SchedulerBackup Wrong State for Action 'Duration Start' for State {0}. SchedulerBackup Trigger Ignored", Enum.GetName(typeof(App.StateSystem), state))); + } + else + { + // Perform Action + if (bStopInsteadOfPause) + StopMonitoring(true, bStopServices); + else + PauseMonitoring(true); + + // Mark Scheduler Backup as Started + App.setting.LastSchedulerBackupRun = dtNow; + } + break; + } + case 2: // Duration End + { + // We are in a wrong state to perform a start + if (state != App.StateSystem.Paused && + state != App.StateSystem.Stopped) + { + App.log.Error(String.Format("SchedulerBackup Wrong State for Action 'Duration End' for State {0}. SchedulerBackup Trigger Ignored", Enum.GetName(typeof(App.StateSystem), state))); + } + else + { + // Perform Action + StartMonitoring(false, true); + } + break; + } + } + } + } + + // Reset the Scheduler Backup State + App.State_SchedulerBackupExecution_Stopped(); + } + + // Re-Start the Scheduler Backup timer * Waiting for the next iteration * + StartSchedulerBackup(99986); + } + } + + /// + /// * Core Logic * Monitor Processes from the Configuration. Start Any Processes that are Ok to + /// be started (ProcessOkToBeStarted_Check) that are currently not running + /// + /// Monitor Configuration + /// Max Failure Count + /// Max Failure Count in given Hour Timespan + /// true if successfull, false if ProcessMonitor Threw an Error + private static bool ProcessMonitor(WatchdogConfiguration config, uint nMaxFailureCount, uint nMaxFailureCountHour) + { + // * PROCESS MONITORING * + if (config.ConfiguredHasAnyProcesses) + { + try + { + // Start Any Processes that are NOT Running + uint uProcessStartSuccessCount = 0; + uint uProcessStarFailCount = 0; + List nonRunningProcesses = Processes_ThatNeedToBeStarted_Check(config); + if (nonRunningProcesses.Count > 0) + { + // Iterate each non-running Process and try to Start it + foreach (WatchdogConfiguration.ProcessExe mProcess in nonRunningProcesses) + { + // Check that the Process can be started + if (!ProcessOkToBeStarted_Check(mProcess, nMaxFailureCount, nMaxFailureCountHour)) + continue; + + App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_TO_START_PROCESS", mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms), -1); + uint PID = PStarter.StartProcess(PStartInfo.CreateProcess(mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms, mProcess.WorkingDirectory, false, ProcessWindowStyle.Normal, true), false, false); + if (PID > 0) + { + // Add to Start Attempt, Set the PID, log, and incr. Counter + mProcess.AddStartTime(DateTime.Now, (int)(nMaxFailureCount + 2)); // *Always* Keep the List 2 bigger than nMaxFailureCount, this way we can track up to nMaxFailure + mProcess.PID = PID; + App.log.Info(AppResx.GetStringFormated("MESSAGE_START_PROCESS_SUCCESS", mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms), -1); + App.log.Info(AppResx.GetStringFormated("MESSAGE_START_PROCESS_SUCCESS2", mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms, mProcess.GetStartTimesInGivenTimeSpan(TimeSpan.FromHours(nMaxFailureCountHour)), nMaxFailureCountHour), -1); + uProcessStartSuccessCount++; + } + else + { + // Add to Failure, Reset the PID, log, and incr. Counter + mProcess.AddFailTime(DateTime.Now, (int)(nMaxFailureCount + 2)); // *Always* Keep the List 2 bigger than nMaxFailureCount, this way we can track up to nMaxFailure + mProcess.PID = 0; + App.log.Error(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED", mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms), -1); + App.log.Error(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED2", mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms, mProcess.GetFailTimesInGivenTimeSpan(TimeSpan.FromHours(nMaxFailureCountHour)), nMaxFailureCountHour), -1); + uProcessStarFailCount++; + } + } + + // Imp! - Let the User know Visually that Processes were started or failed to start + if (uProcessStartSuccessCount > 0 && uProcessStarFailCount == 0) + { + App.log.Info(AppResx.GetStringFormated("MESSAGE_PROCESS_N_SUCCESS_COUNT", uProcessStartSuccessCount)); + if(!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_PROCESS_N_SUCCESS_COUNT", (int)uProcessStartSuccessCount)); + } + else if (uProcessStartSuccessCount == 0 && uProcessStarFailCount > 0) + { + App.log.Error(AppResx.GetStringFormated("MESSAGE_PROCESS_N_FAIL_COUNT", uProcessStarFailCount)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_PROCESS_N_FAIL_COUNT", (int)uProcessStarFailCount)); + } + else if (uProcessStartSuccessCount > 0 && uProcessStarFailCount > 0) + { + App.log.Error(AppResx.GetStringFormated("MESSAGE_PROCESS_N_SUCCESS_AND_N_FAIL_COUNT", uProcessStartSuccessCount, uProcessStarFailCount)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_PROCESS_N_SUCCESS_AND_N_FAIL_COUNT", (int)uProcessStartSuccessCount, (int)uProcessStarFailCount)); + } + + // If any Process Changes occured * Refresh the System Tray Notification Area * + if (uProcessStartSuccessCount > 0 || uProcessStarFailCount > 0) + { + if (!App.State_SpecialMode_IsRunAsServiceSet()) + Watchdog.WatchdogLib.Win32.Functions.RefreshTaskbarNotificationArea(); + } + } + else + { + // We are in a * good State * No Configuration was found that isn't running, + // use this time to make sure that our StartedPID Table is in sync * Perform general internal DS Cleanup * + PStarter.PerformGeneralCleanupOfAllStartedProcessIDs(config.MonitoredProcesses.QueryAllPidInfo(false, true)); + } + } + catch (Exception ex) { App.log.Error("Process Monitor Running Error Thrown", ex); return false; } + } + + return true; + } + + /// + /// Checks to see if the process is Ok to be Started. Checks error state as well as timeout values + /// + /// a valid process exe, that is going to be started + /// Max Failure Count + /// Max Failure Count in given Hour Timespan + /// true if it is ok to start, false otherwise + private static bool ProcessOkToBeStarted_Check(WatchdogConfiguration.ProcessExe processExe, uint nMaxFailureCount, uint nMaxFailureCountHour) + { + if (processExe == null) + return false; + + // * Ignore any process marked as errored * + if (processExe.InErrorState) + return false; + + // If there was a failure starting the process before,... check + // the last failed time flag, we don't want to try over and over again, every second, + // but we'll give it a try every minute + if (!processExe.LastFailTimeTimeoutExceeded(TimeSpan.FromMinutes(1))) + { + return false; + } + else if (processExe.GetFailTimesInGivenTimeSpanMaxTryExceeded(new TimeSpan((int)nMaxFailureCountHour, 0, 0), nMaxFailureCount)) + { + // We are now officially in an error state + App.State_Error_SetErrorState(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); + processExe.InErrorState = true; + App.log.Info(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); + return false; // skip Starting of this Process; Start Failure Count too High + } + + // If the process started correctly, but then failed shortly thereafter, + // then we want to keep track of it and make sure that don't try to continue starting it + if (!processExe.LastStartTimeTimeoutExceeded(TimeSpan.FromMinutes(1))) + { + return false; + } + else if (processExe.GetStartTimesInGivenTimeSpanMaxTryExceeded(new TimeSpan((int)nMaxFailureCountHour, 0, 0), nMaxFailureCount)) + { + // We are now officially in an error state + App.State_Error_SetErrorState(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); + processExe.InErrorState = true; + App.log.Info(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); + return false; // skip Starting of this Process; Start Count too High + } + + return true; + } + + /// + /// Static variable * For Performance * used by Processes_ThatNeedToBeStarted_Check() + /// + private static List s_nonRunningProcesses = new List(); + + /// + /// Goes thru the configuration and returns back a list of all processes that need to be started + /// + /// Monitor Configuration + /// List of all processes currently not running that require a new start, empty list if none found + private static List Processes_ThatNeedToBeStarted_Check(WatchdogConfiguration config) + { + // Clear Non-Running List + s_nonRunningProcesses.Clear(); + + // We don't want to run thru all command-line parameters all the time * Time-Hog * + // ~instead we want to check our run-time configuration to see if we have any start/fail information, + // if we don't, then we MUST run thru everything, because this means our PID info will be out of date + // ~If we are in an Error state, don't perform in-depth search, otherwise once the error state is reached + // we'll always fail here and always perform the in-depth search pointlessly since we'll never launch that process + bool bPerformInDepthProcessSearch = config.MonitoredProcesses.HasIncompletePidInformation() && (App.State_GetSystemState() != App.StateSystem.Error); + if (bPerformInDepthProcessSearch) + { + // Cache Process Lookups * for performance * + ProcessW.ClearProcessObjsGetterCache(); + foreach (WatchdogConfiguration.ProcessExe mProcess in config.MonitoredProcesses.ProcessExes) + { + // Immediatly Cache the Process Getter Calls * Better Performance * + Process[] RunningProcessesOfExe = ProcessW.AllRunningProcessesOf(mProcess.ProcessName, true, true); + + // Perform ExactProcessExeNCommandLinePrms Matching + Process rProcess = null; + int nProcessIndex = -1; + TraceM.TraceBegin(); + bool bFoundMatch = ProcessW.FoundExactProcessExeNCommandLinePrmsInRunningProcesses(RunningProcessesOfExe, mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms, (int)mProcess.PID, out rProcess, out nProcessIndex); + TraceM.TraceEnd("Calling FoundExactProcessExeNCommandLinePrmsInRunningProcessess"); + + // If no Match was found in the Running Processes, add to the list + if (!bFoundMatch) + { + s_nonRunningProcesses.Add(mProcess); + } + else if(rProcess != null) // we found a match * update the configuration * + { + // Update the Pid in our configuration + mProcess.PID = (uint)rProcess.Id; + rProcess = null; + } + } + } + else // Quick Search * Better Performances * + { + // Fastest way to check for PID changes is to retrieve + // all Pids on the system once and then do everything in memory + TraceM.TraceBegin(); + List AllComputerPIDs = ProcessW.AllRunningPids(); + foreach (uint pid in config.MonitoredProcesses.QueryAllPidInfo(false, true)) + { + if (!AllComputerPIDs.Contains(pid)) + { + WatchdogConfiguration.ProcessExe p = config.MonitoredProcesses.GetProcessExeForPid(pid); + if(p != null) + s_nonRunningProcesses.Add(p); + } + } + TraceM.TraceEnd("Going Thru Applications Pid-by-Pid"); + } + + // Return all Processes that aren't running + return s_nonRunningProcesses; + } + + /// + /// Iterates thru the Configured Processes and Try's to Kill Any Processes that + /// are running. + /// + private static void ProcessKillAllMonitoredProcessesIfAny(WatchdogConfiguration config) + { + // Kill all Processes that exist in our configuration + uint nAssumedRunningProcesses = 0; + uint nErroredProcesses = 0; + uint nProcessesKilled = 0; + + // Allow the User to not kill certain processes via the Restart Scheduler + bool bIncludeNonRestartProcesses = !App.State_IsSchedulerExecuting(); + + // Check Configuration + if (config.ConfiguredHasAnyProcesses) + { + nErroredProcesses = config.MonitoredProcesses.NumberOfPidsInErrorState(); + if (nErroredProcesses > 0) + App.log.Info(AppResx.GetStringFormated("MESSAGE_NPROCESSES_IN_AN_ERROR_STATE", App.APPLICATION_NAME_SHORT, nErroredProcesses)); + + nAssumedRunningProcesses = (uint)config.MonitoredProcesses.QueryAllPidInfo(false, bIncludeNonRestartProcesses).Length; + if (nAssumedRunningProcesses > 0) + App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_TO_CLOSE_N_PROCESSES", App.APPLICATION_NAME_SHORT, nAssumedRunningProcesses)); + + if (nAssumedRunningProcesses <= 0) // nothing to do here + return; + } + else + { + return; // nothing to do here + } + + // Iterate the Configuration * Use the PIDs whe have stored * + foreach (uint pid in config.MonitoredProcesses.QueryAllPidInfo(false, bIncludeNonRestartProcesses)) + { + WatchdogConfiguration.ProcessExe p = config.MonitoredProcesses.GetProcessExeForPid(pid); + bool bSuccess = PStarter.KillProcess(pid, false, 1, true, 2); + if (bSuccess) + { + App.log.Info(AppResx.GetStringFormated("MESSAGE_CLOSE_PID_SUCCESS", pid, p.ProcessExeFileNameNPath, p.CommandLinePrms), -1); + nProcessesKilled++; + } + else + { + App.log.Error(AppResx.GetStringFormated("MESSAGE_CLOSE_PID_FAILURE", pid, p.ProcessExeFileNameNPath, p.CommandLinePrms), -1); + } + } + + // Refresh the taskbar, after killing all the processes + if(nProcessesKilled > 0 && !App.State_SpecialMode_IsRunAsServiceSet()) + Watchdog.WatchdogLib.Win32.Functions.RefreshTaskbarNotificationArea(); + + // Let the User know that errors occured + if (App.State_GetSystemState() != App.StateSystem.Error) /* if We already are in an error state, no need to pop-up a message again */ + { + if (nProcessesKilled != nAssumedRunningProcesses) + { + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_ABOUT_CLOSE_N_PROCESSES_FAILURE", (int)(nAssumedRunningProcesses - nProcessesKilled))); + App.log.Error(AppResx.GetStringFormated("MESSAGE_ABOUT_CLOSE_N_PROCESSES_FAILURE", (config.MonitoredProcesses.ProcessExes.Length - nProcessesKilled))); + } + else + { + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_ABOUT_CLOSE_N_PROCESSES_SUCCESS", nProcessesKilled)); + App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_CLOSE_N_PROCESSES_SUCCESS", nProcessesKilled)); + } + } + } + + /// + /// * Core Logic * Monitor Services from the Configuration. Start Any Services that are Ok to + /// be started (ServiceOkToBeStarted_Check) that are currently not running + /// + /// Monitor Configuration + /// Max Failure Count + /// Max Failure Count in given Hour Timespan + /// true if successfull, false if ServiceMonitor Threw an Error + private static bool ServiceMonitor(WatchdogConfiguration config, uint nMaxFailureCount, uint nMaxFailureCountHour) + { + // * SERVICE MONITORING * + if (config.ConfiguredHasAnyServices) + { + try + { + // Check To see if the Service is up and running, if it isn't, restart it * simple stuff * + uint uServiceStartSuccessCount = 0; + uint uServiceStartFailCount = 0; + foreach (WatchdogConfiguration.ServiceExe s in config.MonitoredServices.ServiceExes) + { + // Start the Service up to Five Times * Simplified way of doing this * + TraceM.TraceBegin(); + bool bIsServiceRunning = ServiceW.IsServiceRunning(s.Name); + TraceM.TraceEnd("Checking Service is Running"); + + if (!bIsServiceRunning) + { + // Check that the Service can be started + if (!ServiceOkToBeStarted_Check(s, nMaxFailureCount, nMaxFailureCountHour)) + continue; + + // * Try to Restart the Service * + TraceM.TraceBegin(); + App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_TO_RESTART_SERVICE", s.Name), -1); + bool bRestartOk = ServiceW.RestartService(s.Name, true, 240 , 2); + TraceM.TraceEnd("Restarting Service"); + + if (bRestartOk) + { + // Add to Start Attempt, Log the Successful Launch, incr. Start Service Counter + s.AddStartTime(DateTime.Now, (int)(nMaxFailureCount + 2)); // *Always* Keep the List 2 bigger than nMaxFailureCount, this way we can track up to nMaxFailure + App.log.Info(AppResx.GetStringFormated("MESSAGE_START_SERVICE_SUCCESS", s.Name, s.GetStartTimesInGivenTimeSpan(TimeSpan.FromHours(nMaxFailureCountHour)), nMaxFailureCountHour), -1); + uServiceStartSuccessCount++; + } + else + { + // Add to Failure, Log the Errored Launch, incr. Failed Service Counter + s.AddFailTime(DateTime.Now, (int)(nMaxFailureCount + 2)); // *Always* Keep the List 2 bigger than nMaxFailureCount, this way we can track up to nMaxFailure + App.log.Error(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED", s.Name, s.GetFailTimesInGivenTimeSpan(TimeSpan.FromHours(nMaxFailureCountHour)), nMaxFailureCountHour), -1); + uServiceStartFailCount++; + } + } + } + + // Imp! - Let the User know Visually that Processes were started or failed to start + if (uServiceStartSuccessCount > 0 && uServiceStartFailCount == 0) + { + App.log.Info(AppResx.GetStringFormated("MESSAGE_SERVICE_N_SUCCESS_COUNT", uServiceStartSuccessCount)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_SERVICE_N_SUCCESS_COUNT", (int)uServiceStartSuccessCount)); + } + else if (uServiceStartSuccessCount == 0 && uServiceStartFailCount > 0) + { + App.log.Error(AppResx.GetStringFormated("MESSAGE_SERVICE_N_FAIL_COUNT", uServiceStartFailCount)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_SERVICE_N_FAIL_COUNT", (int)uServiceStartFailCount)); + } + else if (uServiceStartSuccessCount > 0 && uServiceStartFailCount > 0) + { + App.log.Error(AppResx.GetStringFormated("MESSAGE_SERVICE_N_SUCCESS_AND_N_FAIL_COUNT", uServiceStartSuccessCount, uServiceStartFailCount)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_SERVICE_N_SUCCESS_AND_N_FAIL_COUNT", (int)uServiceStartSuccessCount, (int)uServiceStartFailCount)); + } + } + catch (Exception ex) { App.log.Error("Error Thrown in Service Monitor", ex); return false; }; + } + return true; + } + + /// + /// Checks to see if the service is Ok to be Started. Checks error state as well as timeout values + /// + /// a valid service exe, that is going to be started + /// Max Failure Count + /// Max Failure Count in given Hour Timespan + /// true if it is ok to start, false otherwise + private static bool ServiceOkToBeStarted_Check(WatchdogConfiguration.ServiceExe serviceExe, uint nMaxFailureCount, uint nMaxFailureCountHour) + { + if (serviceExe == null) + return false; + + // * Ignore any services marked as errored * + if (serviceExe.InErrorState) + return false; + + // If there was a failure starting the Service before,... check + // the last failed time flag, we don't want to try over and over again, every second, + // but we'll give it a try every minute + if (!serviceExe.LastFailTimeTimeoutExceeded(TimeSpan.FromMinutes(1))) + { + return false; + } + else if (serviceExe.GetFailTimesInGivenTimeSpanMaxTryExceeded(new TimeSpan((int)nMaxFailureCountHour, 0, 0), nMaxFailureCount)) // Default 1 Hour + { + // We are now officially in an error state + App.State_Error_SetErrorState(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); + serviceExe.InErrorState = true; + App.log.Error(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); + return false; // skip Starting of this Service; Start Failure Count too High + } + + // If the service started correctly, but then failed shortly thereafter, + // then we want to keep track of it and make sure that don't try to continue starting it + if (!serviceExe.LastStartTimeTimeoutExceeded(TimeSpan.FromMinutes(1))) + { + return false; + } + else if (serviceExe.GetStartTimesInGivenTimeSpanMaxTryExceeded(new TimeSpan((int)nMaxFailureCountHour, 0, 0), nMaxFailureCount)) // Default 1 Hour + { + // We are now officially in an error state + App.State_Error_SetErrorState(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); + serviceExe.InErrorState = true; + App.log.Error(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); + return false; // skip Starting of this Service; Start Count too High + } + + return true; + } + + /// + /// Iterates thru the Configured Processes and Try's to Kill Any Services that + /// are running and configured to be Stopped. + /// + /// Set to true to force shutting down of services, false otherwise + //private static void ServicesKillAllMonitoredServicesIfAny(WatchdogConfiguration config, bool bForceShutdownOfServices = false) + private static void ServicesKillAllMonitoredServicesIfAny(WatchdogConfiguration config, bool bForceShutdownOfServices) + { + // Should we shutdown Services??? + bool bRestartServices = bForceShutdownOfServices || (App.State_IsSchedulerExecuting() && App.setting.SchedulerRestartsServices); + if (bRestartServices) + { + int nServicesClosed = 0; + int nServicesNotClosed = 0; + + // Allow the User to not kill certain services via the Restart Scheduler + bool bIncludeNonRestartServices = !App.State_IsSchedulerExecuting(); + if (config.ConfiguredHasAnyServices) + App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_TO_CLOSE_N_SERVICES", App.APPLICATION_NAME_SHORT, config.MonitoredServices.QueryAllServiceInfo(false, bIncludeNonRestartServices).Length)); + + foreach (string ServiceName in config.MonitoredServices.QueryAllServiceInfo(false, bIncludeNonRestartServices)) + { + // This is only getting called when the scheduler is executing... + // the scheduler will be responsible for starting the monitor which in turn will start the services and keep track of all + // service starts, hence all we have to do here is shut down the services * they will automatically be restarted * + App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_TO_CLOSE_SERVICE", ServiceName), -1); + bool bCloseServiceSuccess = ServiceW.StopService(ServiceName, true, 240); + if (bCloseServiceSuccess) + { + App.log.Info(AppResx.GetStringFormated("MESSAGE_CLOSE_SERVICE_SUCCESS", ServiceName), -1); + nServicesClosed++; + } + else + { + App.log.Error(AppResx.GetStringFormated("MESSAGE_CLOSE_SERVICE_FAILURE", ServiceName), -1); + nServicesNotClosed++; + } + } + + // Imp! - Let the User know Visually that Services were Stopped + if (nServicesClosed > 0 && nServicesNotClosed == 0) + { + App.log.Info(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_SUCCESS_COUNT", nServicesClosed)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_SUCCESS_COUNT", nServicesClosed)); + } + else if (nServicesClosed == 0 && nServicesNotClosed > 0) + { + App.log.Error(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_FAIL_COUNT", nServicesNotClosed)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_FAIL_COUNT", nServicesNotClosed)); + } + else if (nServicesClosed > 0 && nServicesNotClosed > 0) + { + App.log.Error(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_SUCCESS_AND_N_FAIL_COUNT", nServicesClosed, nServicesNotClosed)); + if (!App.State_SpecialMode_IsRunAsServiceSet()) + WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_SUCCESS_AND_N_FAIL_COUNT", nServicesClosed, nServicesNotClosed)); + } + } + } + + #endregion + + #region Loaded/Unloaded Event Handlers + + /// + /// Handles the Main Window StartUp event. + /// + private void Window_Loaded(object sender, RoutedEventArgs e) + { + // * To make sure, we are not fatally closing * + if (App.s_FatalErrorOccured) + return; + + // * To make sure, do nothing in command-line mode * + if (App.State_SpecialMode_IsCommandLineSet()) + return; + + if (!App.State_SpecialMode_IsRunAsServiceSet()) + { + // Performance Load WPF Windows + WPFWindows.WPFWindowMaker.PerformanceCache(); + + // Set Registry key to Start w. Windows, if specified + bool bStartWithWindows = App.setting.StartWithWindows; + App.log.Info(String.Format("Starting with Windows is set to: '{0}'", bStartWithWindows)); + } + + // Ensure proper existence of HardLink, if specified + bool bCreateHardLink = App.setting.CreateExeHardLinkInRootPath; + App.log.Info(String.Format("Creating .Exe HardLink in Root Path is set to: '{0}'", bCreateHardLink)); + + // If we are deployed by ClickOnce, let's modify the uninstall icon (Add/Remove) Programs + // ~there appears to be no better way than modifying the registry manually * Now Add/Remove will have a nice + // little App icon instead of the default ClickOnce Icon * + string strKeyFetched = RegKey.GetKey("Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + App.APPLICATION_PRODUCT_CLICKONCE_ID, "DisplayIcon", ""); + if(!String.IsNullOrEmpty(strKeyFetched)) + RegKey.SetKey("Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + App.APPLICATION_PRODUCT_CLICKONCE_ID, "DisplayIcon", AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Entry)); + + // Sleep a little before showing the System Tray + System.Threading.Thread.Sleep(250); + + if (!App.State_SpecialMode_IsRunAsServiceSet()) + { + // Refresh the taskbar, when launching (maybe there are trailing instances) + Watchdog.WatchdogLib.Win32.Functions.RefreshTaskbarNotificationArea(); + + // Show the System Tray Icon/Menu + WatchdogSysTray.WatchdogSysTray_Show(); + } + + // Start Process Monitoring upon start? * Default is Yes * + if (App.setting.StartMonitorUponStart) + { + // We should only start the monitor, if there is any configuration + if (App.xmlfile.ReadData(false).ConfiguredHasAny) + { + StartMonitoring(false, true); + } + else if (!App.State_SpecialMode_IsRunAsServiceSet()) + { + WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_NO_CONFIGURATION_FOUND", App.APP_XML_DS_FILENAME)); + WPFWindows.WPFWindowMaker.CreateNShowWindow(WPFWindows.WPFWindow.Settings_Window); + } + else + { + // Nothing to do Here (we must be in Service Mode), without a configuration + App.FatalExceptionStopExecution("No Configuration Found in ServiceMode", false); + } + } + } + + /// + /// Handles the Main Window Exit event. + /// + private void Window_Closed(object sender, EventArgs e) + { + // * To make sure, we are not fatally closing * + if (App.s_FatalErrorOccured) + return; + + // * To make sure, do nothing in command-line mode * + if (App.State_SpecialMode_IsCommandLineSet()) + return; + + // for unexpected cleanup + s_WindowCloseGotCalled = true; + + // Stop the Process Monitor + StopMonitoring(false, false); + + // Make sure the latest DS is saved to file + App.xmlfile.SaveData(); + + if (!App.State_SpecialMode_IsRunAsServiceSet()) + { + // Close the System Tray Icon/Menu + WatchdogSysTray.WatchdogSysTray_Hide(); + + // Unload All Windows (incl. Performance) * Close Log Tail if exists Prior * + LogViewerWindow.CloseFileTailBeforeExitIfExists(); + WPFWindows.WPFWindowMaker.CloseAll(); + } + } + + /// + /// Handle Specialized Cleanup + /// + void CurrentDomain_ProcessExit(object sender, EventArgs e) + { + try + { + // Stop Internal Timers + StopTimer(); + StopSchedulerTimer(); + StopSchedulerBackupTimer(); + + if (_timer != null) + _timer.Dispose(); + + if (_timerScheduler != null) + _timerScheduler.Dispose(); + + if (_timerSchedulerBackup != null) + _timerSchedulerBackup.Dispose(); + + // Make sure that the close get's called * Unexpected cleanup * + if (!s_WindowCloseGotCalled) + Window_Closed(null, null); + } + catch (Exception) { /* ignore */ } + } + + #endregion + } +} diff --git a/@integrate/InstallComponent.cs b/@integrate/InstallComponent.cs new file mode 100644 index 0000000..a27f00d --- /dev/null +++ b/@integrate/InstallComponent.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Component.All; +using BridgeConnector.Lib.File; + +namespace Component.Binary.Mirth +{ + public class InstallComponent : IInstallComponent , ISetup + { + public const string MIRTH_SERVICE_NAME = Common_MediLytec.MediLytecPoundDef.MIRTH_SERVICE_NAME; + private const string POSTGRE_SERVICE_NAME = Common_MediLytec.MediLytecPoundDef.POSTGRE_SERVICE_NAME; + + #region IInstallComponent Members + + public ComponentConfig.Component GetComponent() + { + return Common.EmbeddedConfig.BinaryComponents.GetComponent("Mirth"); + } + + public bool BEFORE_INSTALLING_COMPONENT() + { + bool bSuccess = Common.StopService(MIRTH_SERVICE_NAME); + if (!bSuccess) + { + Common.Log.Error(String.Format("Failed to stop {0}", MIRTH_SERVICE_NAME)); + return false; + } + + string result = Common.RunCmdLine("netsh firewall set portopening tcp 8080 MirthConnectAdministrator ENABLE ALL"); + bSuccess = result.Contains("successfully") || result.Contains("Ok.") || result.Contains("The service has not been started"); + if (bSuccess) + { + result = Common.RunCmdLine("netsh firewall set portopening tcp 8443 MirthConnectServer ENABLE ALL"); + bSuccess = result.Contains("successfully") || result.Contains("Ok.") || result.Contains("The service has not been started"); + } + if (bSuccess) + { + result = Common.RunCmdLine("netsh firewall set portopening tcp 1099 MirthConnectJMX ENABLE ALL"); + bSuccess = result.Contains("successfully") || result.Contains("Ok.") || result.Contains("The service has not been started"); + } + if (!bSuccess) + { + Common.Log.Error(String.Format("Errors occured opening up ports for '{0}'", MIRTH_SERVICE_NAME)); + } + return bSuccess; + } + + public bool INSTALL_COMPONENT() + { + Common.PSetupSpwan(GetComponent().TempFileNamesNPath[0], "-q -overwrite"); + return true; + } + + public bool AFTER_INSTALLING_COMPONENT() + { + if (!Common.ServiceExists(MIRTH_SERVICE_NAME)) + { + Common.Log.Error(String.Format("Service '{0}' does not exist. Something went wrong with Setup", MIRTH_SERVICE_NAME)); + return false; + } + // Make sure Service is stopped + Common.StopService(MIRTH_SERVICE_NAME); + + // # Configure Mirth to work with PostgreSQL, if possible + if (Common.ServiceExists(POSTGRE_SERVICE_NAME)) + { + //SetupMirthToUsePostgreSQL("mirthdb", "postgres", "Clinical$1"); + } + return true; + } + + public bool SUPPORTS_UNINSTALL() + { + return false; + } + + public bool BEFORE_UNINSTALLING_COMPONENT() + { + return false; + } + + public bool UNINSTALL_COMPONENT() + { + return false; + } + + public bool AFTER_UNINSTALLING_COMPONENT() + { + return false; + } + + #endregion + + #region ISetup Members + + public void ComponentLoaded(ref SetupEvents subscribeToDesiredEvents) + { + subscribeToDesiredEvents.Before_Install += new SetupEvents.SetupEvent(subscribeToDesiredEvents_Before_Install); + subscribeToDesiredEvents.After_Install += new SetupEvents.SetupEvent(subscribeToDesiredEvents_After_Install); + subscribeToDesiredEvents.Ending_Setup += new SetupEvents.SetupEvent(subscribeToDesiredEvents_Ending_Setup); + } + + void subscribeToDesiredEvents_Ending_Setup() + { + // Make Sure at the end that both Mirth and Postgresql are started + Common.StartService(POSTGRE_SERVICE_NAME); + Common.StartService(MIRTH_SERVICE_NAME); + } + + void subscribeToDesiredEvents_Before_Install() + { + } + + void subscribeToDesiredEvents_After_Install() + { + } + + #endregion + + #region Private Helpers + + /// + /// Function is responsible fore writing Postgresql Information to the Mirth Configuration + /// * The Mirth Service needs to be stopped/started for this configuration to take affect, + /// ideally this should only be called with the mirth service stopped * + /// + private void SetupMirthToUsePostgreSQL(string dbname, string user, string password) + { + // Open Mirth Configuration + LineReplacer replacer = new LineReplacer((Common.GetProgramFilesPathOnSystemWithEndSlash() + "Mirth Connect\\conf\\mirth.properties"), Encoding.ASCII); + + LineReplace_Rule DBurl = new LineReplace_Rule(); + DBurl.StartsWith = "database.url ="; + DBurl.ReplaceLineWith = String.Format("database.url = jdbc:postgresql://localhost:5432/{0}", dbname); + DBurl.Comparer = LineReplace_ComparerModifier.None; + replacer.AddUpdateRule("DBurl", DBurl); + + LineReplace_Rule DBuser = new LineReplace_Rule(); + DBuser.StartsWith = "database.username ="; + DBuser.ReplaceLineWith = String.Format("database.username = {0}", user); + DBuser.Comparer = LineReplace_ComparerModifier.None; + replacer.AddUpdateRule("DBuser", DBuser); + + LineReplace_Rule DBpass = new LineReplace_Rule(); + DBpass.StartsWith = "database.password ="; + DBpass.ReplaceLineWith = String.Format("database.password = {0}", password); + DBpass.Comparer = LineReplace_ComparerModifier.None; + replacer.AddUpdateRule("DBpass", DBpass); + + // Replace Lines in Mirth Configuration + replacer.ReplaceLines(); + } + + #endregion + } +} diff --git a/@integrate/InstallComponent2.cs b/@integrate/InstallComponent2.cs new file mode 100644 index 0000000..33b229d --- /dev/null +++ b/@integrate/InstallComponent2.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Component.All; +using Microsoft.Win32; + +namespace Component.Binary.Java +{ + public class InstallComponent : IInstallComponent , ISetup + { + #region IInstallComponent Members + + /// + /// Get the Component bound to this IInstallComponent + /// + /// + public ComponentConfig.Component GetComponent() + { + return Common.EmbeddedConfig.BinaryComponents.GetComponent("Java"); + } + + /// + /// We only want to install Java if an earlier version is installed + /// + /// true, if an earlier version or none is installed, false otherwise + public bool BEFORE_INSTALLING_COMPONENT() + { + // If we get here, just in case always just install the java version + // to make sure everything works as expected + + //string Version = GetLatestJavaVersionFromTheRegistry(); + //if (!String.IsNullOrEmpty(Version)) + //{ + // int nCompare = String.Compare(Version, GetComponent().Version); + // if (nCompare < 0) + // return true; + // else + // return false; + //} + return true; + } + + /// + /// Installs Java on the system + /// + /// Returns true if the Component installs, false otherwise + public bool INSTALL_COMPONENT() + { + Common.PSetupSpwan(GetComponent().TempFileNamesNPath[0], "/s /v \"/qn ADDLOCAL=ALL IEXPLORER=1 REBOOT=Supress\""); + return true; + } + + /// + /// Make sure that the version in the registry matches the component + /// + /// true, if the version in the registry matches what we installed, false otherwise + public bool AFTER_INSTALLING_COMPONENT() + { + string Version = GetLatestJavaVersionFromTheRegistry(); + if (!String.IsNullOrEmpty(Version)) + { + int nCompare = String.Compare(GetComponent().Version, Version); + if (nCompare != 0) + return false; + else + return true; + } + return false; + } + + /// + /// Check to see, if this component supports uninstalling + /// + /// true, if uninstall is supported, false otherwise + public bool SUPPORTS_UNINSTALL() + { + return true; + } + + /// + /// Check if we can uninstall the java version + /// + /// true, if version is installed, false otherwise + public bool BEFORE_UNINSTALLING_COMPONENT() + { + string[] Versions = GetAllJavaVersionFromTheRegistry(); + if (Versions != null && Versions.Length > 0) + { + if (Versions.Contains(GetComponent().Version)) + return true; + } + return false; + } + + /// + /// Uninstalls Java on the system + /// + /// Returns true if the Component installs, false otherwise + public bool UNINSTALL_COMPONENT() + { + Common.PSetupSpwan(GetComponent().TempFileNamesNPath[0], "/s /v \"/qn REBOOT=Supress\" /x"); + return true; + } + + /// + /// Check to make sure that the version we uninstalled doesn't exist anymore + /// + /// true if version was removed, false otherwise + public bool AFTER_UNINSTALLING_COMPONENT() + { + string[] Versions = GetAllJavaVersionFromTheRegistry(); + if (Versions != null && Versions.Length > 0) + { + if (Versions.Contains(GetComponent().Version)) + return false; + } + return true; + } + + #endregion + + #region ISetup Members + + public void ComponentLoaded(ref SetupEvents subscribeToDesiredEvents) + { + subscribeToDesiredEvents.Before_Install += new SetupEvents.SetupEvent(subscribeToDesiredEvents_Before_Install); + subscribeToDesiredEvents.After_Install += new SetupEvents.SetupEvent(subscribeToDesiredEvents_After_Install); + } + + void subscribeToDesiredEvents_Before_Install() + { + } + + void subscribeToDesiredEvents_After_Install() + { + } + + #endregion + + #region Private Helpers + + /// + /// Use this function to check the java version in the registry + /// + /// The Java Version found in the registry or String.Empty, if not found + string GetLatestJavaVersionFromTheRegistry() + { + string[] Versions = GetAllJavaVersionFromTheRegistry(); + if (Versions != null && Versions.Length > 0) + { + string strVersion = Versions[Versions.Length - 1]; + if (!String.IsNullOrEmpty(strVersion)) + return strVersion; + } + return String.Empty; + } + + /// + /// Use this function to check all the java versions in the registry + /// + /// The Java Versions found in the registry or null, if none found + string[] GetAllJavaVersionFromTheRegistry() + { + bool bIsJavaInstalled = false; + try + { + RegistryKey reg = Registry.LocalMachine.OpenSubKey("Software\\JavaSoft\\Java Plug-in", false); + bIsJavaInstalled = (reg != null); + if(reg == null) + reg = Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\JavaSoft\\Java Plug-in", false); + bIsJavaInstalled = (reg != null); + if (bIsJavaInstalled) + { + string[] SubKeys = reg.GetSubKeyNames(); + if (SubKeys != null && SubKeys.Length > 0) + return SubKeys; + } + } + catch (Exception) { /* ignore */ } + return null; + } + + #endregion + } +} diff --git a/@integrate/InstallComponent4.cs b/@integrate/InstallComponent4.cs new file mode 100644 index 0000000..1e55c09 --- /dev/null +++ b/@integrate/InstallComponent4.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Component.All; +using Microsoft.Win32; +using BridgeConnector.Lib.Assembly; +using System.IO; +using BridgeConnector.Lib.XML; +using BridgeConnector; + +namespace Component.Setup.BridgeService +{ + public class InstallComponent : IInstallComponent , ISetup + { + #region Private Helpers + + public const string BRIDGE_SERVICE_TITLE = Common_MediLytec.MediLytecPoundDef.BRIDGE_SERVICE_TITLE; + public const string BRIDGE_SERVICE_ASSEMBLY = Common_MediLytec.MediLytecPoundDef.BRIDGE_SERVICE_ASSEMBLY; + public const string BRIDGE_SERVICE_NAME = Common_MediLytec.MediLytecPoundDef.BRIDGE_SERVICE_NAME; + + /// + /// Write Service Configuration to the ImagePath for the Service + /// + /// + public static bool WriteServiceConfigurationToServiceInRegistry(InstallConfig config) + { + try + { + using (RegistryKey service = Registry.LocalMachine.OpenSubKey("System\\CurrentControlSet\\services\\" + BRIDGE_SERVICE_TITLE, true)) + { + // # Determine Image Path Parameter + string ImagePathParameter = ""; + switch (config) + { + case InstallConfig.LytecMD: + ImagePathParameter = " /Lytec"; + break; + + case InstallConfig.MedisoftClinical: + ImagePathParameter = " /Medisoft"; + break; + } + + // # Set Image Path Parameter + string serviceAssembly = "\"" + AssemblyW.SpecializedAssemblyInfo.GetAssemblyPath(AssemblyW.AssemblyST.Executing) + "\\" + BRIDGE_SERVICE_ASSEMBLY + "\""; + service.SetValue("ImagePath", serviceAssembly + ImagePathParameter); + return true; + } + } + catch (Exception) { /* ignore */ } + return false; + } + + #endregion + + #region IInstallComponent Members + + public ComponentConfig.Component GetComponent() + { + return Common.EmbeddedConfig.SetupComponents.GetComponent("BridgeService"); + } + + public bool BEFORE_INSTALLING_COMPONENT() + { + // Stop Service BRIDGE_SERVICE_NAME + if (!Common.StopService(BRIDGE_SERVICE_NAME)) + { + Common.Log.Error(String.Format("Couldn't stop the {0} Service.", BRIDGE_SERVICE_NAME)); + return false; + } + + // Read the Service Configuration from the Registry + InstallConfig config = Common_MediLytec.RetrieveInstallConfigFromRegistry(); + if (config == InstallConfig.LytecMD) + { + bool bSuccess = false; + string result = Common.RunCmdLine("netsh firewall set portopening tcp 5000 BridgeService_LytecBridgeIn ENABLE ALL"); + bSuccess = result.Contains("successfully") || result.Contains("Ok.") || result.Contains("The service has not been started"); + if (bSuccess) + { + result = Common.RunCmdLine("netsh firewall set portopening tcp 5001 BridgeService_LytecMirthOut ENABLE ALL"); + bSuccess = result.Contains("successfully") || result.Contains("Ok.") || result.Contains("The service has not been started"); + } + if (bSuccess) + { + return true; + } + else + { + Common.Log.Error(String.Format("Opening up ports for Lytec '{0}' failed", BRIDGE_SERVICE_NAME)); + return false; + } + } + else if (config == InstallConfig.MedisoftClinical) + { + bool bSuccess = false; + string result = Common.RunCmdLine("netsh firewall set portopening tcp 7000 BridgeService_MedisoftBridgeIn ENABLE ALL"); + bSuccess = result.Contains("successfully") || result.Contains("Ok.") || result.Contains("The service has not been started"); + if (bSuccess) + { + result = Common.RunCmdLine("netsh firewall set portopening tcp 7001 BridgeService_MedisoftMirthOut ENABLE ALL"); + bSuccess = result.Contains("successfully") || result.Contains("Ok.") || result.Contains("The service has not been started"); + } + if (bSuccess) + { + return true; + } + else + { + Common.Log.Error(String.Format("Opening up ports for Medisoft '{0}' failed", BRIDGE_SERVICE_NAME)); + return false; + } + } + return true; + } + + /// + /// + /// + /// + public bool INSTALL_COMPONENT() + { + // Uninstall Bridge Service, if it exists, and re-install + if (Common.ServiceExists(BRIDGE_SERVICE_NAME)) + UNINSTALL_COMPONENT(); + + string installUtil = Common.GetNetFrameworkUtilFileNameNPathFile("installutil.exe"); + string serviceAssembly = AssemblyW.SpecializedAssemblyInfo.GetAssemblyPath(AssemblyW.AssemblyST.Entry) + "\\" + BRIDGE_SERVICE_ASSEMBLY; + if (!String.IsNullOrEmpty(installUtil)) + { + Common.StopService(BRIDGE_SERVICE_NAME); + string result = Common.RunCmdLine(installUtil + " \"" + serviceAssembly + "\""); + bool bSuccess = !result.Contains("failed"); + if (bSuccess) + { + // Write the Service Configuration to the Registry + InstallConfig config = Common_MediLytec.RetrieveInstallConfigFromRegistry(); + bSuccess = WriteServiceConfigurationToServiceInRegistry(config); + } + if (!bSuccess) + { + Common.Log.Error(String.Format("Errors Occured installing {0}.", BRIDGE_SERVICE_NAME)); + } + return bSuccess; + } + return false; + } + + public bool AFTER_INSTALLING_COMPONENT() + { + // Make sure service exists + if (!Common.ServiceExists(BRIDGE_SERVICE_NAME)) + { + Common.Log.Error(String.Format("Service {0} does Not Exist. Install Failed", BRIDGE_SERVICE_NAME)); + return false; + } + + // Make sure Service is stopped + Common.StopService(BRIDGE_SERVICE_NAME); + return true; + } + + public bool SUPPORTS_UNINSTALL() + { + return false; + } + + public bool BEFORE_UNINSTALLING_COMPONENT() + { + // Stop Service BRIDGE_SERVICE_NAME + if (!Common.StopService(BRIDGE_SERVICE_NAME)) + { + Common.Log.Error(String.Format("Couldn't stop the {0} Service.", BRIDGE_SERVICE_NAME)); + return false; + } + return true; + } + + public bool UNINSTALL_COMPONENT() + { + if (Common.ServiceExists(BRIDGE_SERVICE_NAME)) + { + Common.StopService(BRIDGE_SERVICE_NAME); + string installUtil = Common.GetNetFrameworkUtilFileNameNPathFile("installutil.exe"); + string serviceAssembly = AssemblyW.SpecializedAssemblyInfo.GetAssemblyPath(AssemblyW.AssemblyST.Entry) + "\\" + BRIDGE_SERVICE_ASSEMBLY; + if (!String.IsNullOrEmpty(installUtil)) + { + string result = Common.RunCmdLine(installUtil + " /u \"" + serviceAssembly + "\""); + bool bSuccess = !result.Contains("failed"); + if(!bSuccess) + Common.Log.Error(String.Format("Errors Occured uninstalling {0}.", BRIDGE_SERVICE_NAME)); + return bSuccess; + } + } + return false; + } + + public bool AFTER_UNINSTALLING_COMPONENT() + { + if (Common.ServiceExists(BRIDGE_SERVICE_NAME)) + { + Common.Log.Error(String.Format("Service {0} Still Exists. Uninstall Failed", BRIDGE_SERVICE_NAME)); + return false; + } + return true; + } + + #endregion + + #region ISetup Members + + public void ComponentLoaded(ref SetupEvents subscribeToDesiredEvents) + { + subscribeToDesiredEvents.Before_Install += new SetupEvents.SetupEvent(subscribeToDesiredEvents_Before_Install); + subscribeToDesiredEvents.After_Install += new SetupEvents.SetupEvent(subscribeToDesiredEvents_After_Install); + subscribeToDesiredEvents.Ending_Setup += new SetupEvents.SetupEvent(subscribeToDesiredEvents_Ending_Setup); + } + + void subscribeToDesiredEvents_Ending_Setup() + { + if (Common.ServiceExists(BRIDGE_SERVICE_NAME)) + { + string path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\McKesson\\Bridge\\BridgeConfig.xml"; + if (File.Exists(path)) + { + XSerializer serialize = new XSerializer(); + XMLConfig config = serialize.ReadFromFile(path); + if (config != null) + { + // Read the Service Configuration from the Registry + InstallConfig sconfig = Common_MediLytec.RetrieveInstallConfigFromRegistry(); + if (sconfig == InstallConfig.LytecMD && !String.IsNullOrEmpty(config.DefaultMapping.Lytec)) + Common.StartService(BRIDGE_SERVICE_NAME); + else if(sconfig == InstallConfig.MedisoftClinical && !String.IsNullOrEmpty(config.DefaultMapping.Medisoft)) + Common.StartService(BRIDGE_SERVICE_NAME); + } + } + } + } + + void subscribeToDesiredEvents_Before_Install() + { + + } + + void subscribeToDesiredEvents_After_Install() + { + + } + + #endregion + } +} diff --git a/@integrate/Logging.cs b/@integrate/Logging.cs new file mode 100644 index 0000000..f8e7722 --- /dev/null +++ b/@integrate/Logging.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +// Log4Net Declarations +using log4net.Appender; +using log4net.Core; +using log4net.Layout; +using log4net.Config; +using log4net; +using Diag = System.Diagnostics; +using BridgeConnector.Lib.Other; + +namespace BridgeConnector.Lib.File +{ + /// + /// Logging Detail + /// + public enum Logging_Detail + { + NONE, + ERROR, + INFO, + DEBUG + } + + /// + /// Used to Configure a Logger Instance in the Logging Class via AddGlobalLoggerConfiguration() + /// + public struct Logging_Configuration + { + public string LogFileNameNPath; + public Logging_Detail Detail; + public bool UseExclusiveFileLock; + public int maxFileSizeInMB; + public int numOfBackupLogFiles; + public string PatternLayout; + } + + /// + /// Is a Wrapper Object around Log4Net's Rolling File Appender. + /// Use it by calling AddGlobalLoggerConfiguration() with a valid Logging Configuration. + /// You can configure multipe Logger Instances distinguished by Name. + /// subsequent calls can call GetLogger() with the named Logger instance to receive a valid logger object + /// + public class Logging + { + #region Private Static Members + + private static Dictionary _loggerConfigurationMap = new Dictionary(); + private static Dictionary _loggerObjects = new Dictionary(); + private static bool s_IsVSHosted = false; + + #endregion + + #region Private Construction + + private Logging() { s_IsVSHosted = Diag.Process.GetCurrentProcess().ProcessName.Contains("vshost"); } + + #endregion + + #region Internal Members + + internal ILog _Log4NetLog = null; // Initialized by GetLogger() + + #endregion + + #region Public Log Methods + + public void Info(object message) { if (_Log4NetLog != null) _Log4NetLog.Info(MessageHeader() + message); } + public void Info(object message, int nPlusMinus) { if (_Log4NetLog != null) _Log4NetLog.Info(MessageHeader(nPlusMinus) + message); } + public void Info(object message, Exception exception) { if (_Log4NetLog != null) _Log4NetLog.Info(MessageHeader() + message, exception); } + + public void Debug(object message) { if (_Log4NetLog != null) _Log4NetLog.Debug(MessageHeader() + message); } + public void Debug(object message, int nPlusMinus) { if (_Log4NetLog != null) _Log4NetLog.Debug(MessageHeader(nPlusMinus) + message); } + public void Debug(object message, Exception exception) { if (_Log4NetLog != null) _Log4NetLog.Debug(MessageHeader() + message, exception); } + + public void Error(object message) { if (_Log4NetLog != null) _Log4NetLog.Error(MessageHeader() + message); } + public void Error(object message, int nPlusMinus) { if (_Log4NetLog != null) _Log4NetLog.Error(MessageHeader(nPlusMinus) + message); } + public void Error(object message, Exception exception) { if (_Log4NetLog != null) _Log4NetLog.Error(MessageHeader() + message, exception); } + + public void Fatal(object message) { if (_Log4NetLog != null) _Log4NetLog.Fatal(MessageHeader() + message); } + public void Fatal(object message, int nPlusMinus) { if (_Log4NetLog != null) _Log4NetLog.Fatal(MessageHeader(nPlusMinus) + message); } + public void Fatal(object message, Exception exception) { if (_Log4NetLog != null) _Log4NetLog.Fatal(MessageHeader() + message, exception); } + + /// + /// Message Header to be shown on every log message + /// + private string MessageHeader() + { + if (s_IsVSHosted) + return MessageHeader(0); + else + return StackWalker.GetMethodNameFromStack(-2) + "()- "; + } + + /// + /// Message Header to be shown on every log message + /// + /// Use this to add/substract from the base stack level you want to retrieve + private string MessageHeader(int nPlusMinus) + { + // When Running this from via VS it behaves differently then when + // running it outside of it, when running it regularly, each foreach loop + // is it's own stackframe! ~weird but true. Only use the nPlusMinus when not running + // inside VS + if(s_IsVSHosted) + return StackWalker.GetMethodNameFromStack(nPlusMinus) + "()- "; + else + return StackWalker.GetMethodNameFromStack(nPlusMinus - 1) + "()- "; + } + + #endregion + + #region Public Static Configuration and Logger Creation Methods + + /// + /// Used to add a new Configuration and Logger Instance onto a Static Map. + /// Will create one logger instance per unique name. + /// + /// a name for the logger instance + /// a valid configuration to use on the instance + /// a logging object that can be used to log + public static Logging AddGlobalLoggerConfiguration(string Name, Logging_Configuration Configuration) + { + // Must have a Valid Input + if (string.IsNullOrEmpty(Name)) + throw new ArgumentException("Name Is Invalid"); + + if (!_loggerObjects.Keys.Contains(Name.ToLower())) + { + // Create the Repository + log4net.Repository.ILoggerRepository repository = LogManager.CreateRepository(Name.ToLower()); + + // Create FileAppender Configuration + RollingFileAppender appender = RollingFileAppenderCreator(Configuration); + + // Run the Configuration against the Repository + BasicConfigurator.Configure(repository, appender); + + // Add Configuration to our Static Map + _loggerConfigurationMap[Name.ToLower()] = Configuration; + + // Last, but not least, Create the new Logging Instance Object and Store it + _loggerObjects[Name.ToLower()] = LogManager.GetLogger(Name.ToLower(), Name.ToLower()); + } + + // Let the Caller get the Logging Object + return GetLogger(Name); + } + + /// + /// Used to retrieve a named logging instance that has already been created via a previous call + /// to AddGlobalLoggerConfiguration(). + /// + /// a name for a previously created Logging instance + /// a Logging object that can be used to log + public static Logging GetLogger(string Name) + { + if (_loggerObjects.Keys.Contains(Name.ToLower())) + { + Logging logger = new Logging(); + logger._Log4NetLog = _loggerObjects[Name.ToLower()]; + return logger; + } + else + throw new ArgumentException("Must call AddGlobalLoggerConfiguration() with a Configuration Before calling this Function"); + } + + #endregion + + #region Private Static Helper Methods + + /// + /// Creates a Log4Net RollingFileAppender with the specified configuration + /// + /// a valid configuration + /// a Log4Net RollingFileAppender Object + private static RollingFileAppender RollingFileAppenderCreator(Logging_Configuration config) + { + #region Input Validation + + if (config.maxFileSizeInMB <= 0) + throw new ArgumentException("Logging_Configuration - Invalid maxFileSizeInMB"); + + if (config.numOfBackupLogFiles < 0) + throw new ArgumentException("Logging_Configuration - Invalid numOfBackupLogFiles"); + + if (String.IsNullOrEmpty(config.LogFileNameNPath)) + throw new Exception("Logging_Configuration - Invalid LogFileNameNPath"); + + if (!Directory.Exists(Path.GetDirectoryName(config.LogFileNameNPath))) + Directory.CreateDirectory(Path.GetDirectoryName(config.LogFileNameNPath)); + + #endregion + + // Create and Set Layout for FileAppender + RollingFileAppender rfAppender = new RollingFileAppender(); + if (!String.IsNullOrEmpty(config.PatternLayout)) + rfAppender.Layout = new PatternLayout(config.PatternLayout); + else + rfAppender.Layout = new PatternLayout("%date{dd MMM HH:mm:ss,fff} [%thread] %level - %message%newline"); + //rfAppender.Layout = new PatternLayout("%date{dd MMM yyyy HH:mm:ss,fff} [%thread] %level %logger - %message%newline"); + + // Locking Minimal allows us to run Log4Net from multiple processes + if (config.UseExclusiveFileLock) + rfAppender.LockingModel = new FileAppender.ExclusiveLock(); + else + rfAppender.LockingModel = new FileAppender.MinimalLock(); + + // Configure FileName and always set Appent to true + rfAppender.File = config.LogFileNameNPath; + rfAppender.AppendToFile = true; + + // According to the listings on the blog site + // http://blog.aggregatedintelligence.com/2009/08/log4net-logging-levels-available.html + // Error, will log Error, Fatal + // Info, will log Info, Error, and Fatal + // Debug, will log Info, Error, Fatal and Debug + if (config.Detail == Logging_Detail.NONE) + rfAppender.Threshold = Level.Off; + else if (config.Detail == Logging_Detail.ERROR) + rfAppender.Threshold = Level.Error; + else if (config.Detail == Logging_Detail.INFO) + rfAppender.Threshold = Level.Info; + else if (config.Detail == Logging_Detail.DEBUG) + rfAppender.Threshold = Level.Debug; + + rfAppender.MaximumFileSize = String.Format("{0}MB", config.maxFileSizeInMB); + rfAppender.MaxSizeRollBackups = config.numOfBackupLogFiles; + rfAppender.RollingStyle = RollingFileAppender.RollingMode.Size; // Setting to RollingMode.Size will make MaxSizeRollBackups work + rfAppender.ActivateOptions(); + return rfAppender; + } + + #endregion + } +} diff --git a/@integrate/MsgBox.cs b/@integrate/MsgBox.cs new file mode 100644 index 0000000..de00ef5 --- /dev/null +++ b/@integrate/MsgBox.cs @@ -0,0 +1,482 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Watchdog.WatchdogLib.Win32; +using System.Windows.Forms; +using System.Drawing; +using System.ComponentModel; +using WPFWin = System.Windows; +using WPFWinInterop = System.Windows.Interop; + +namespace Watchdog.WatchdogLib.WinForms +{ + /// + /// MsgBox is a WindowsForms MessageBox that can be centered to the Parent + /// + public static class MsgBox + { + #region Private Static Members + + private static User32.WindowsHookProc _hookProcDelegate = null; + private static int _hHook = 0; + private static string _title = null; + private static string _msg = null; + private static IntPtr _hIcon = IntPtr.Zero; + private static bool _IsDesktopOwner = false; + private static bool _ShowFatal = false; + + // Delegate to make All Message Boxes Thread-Safe + private delegate DialogResult ShowMsgBoxDelegate(IWin32Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons, MessageBoxIcon MsgBoxIcon, Icon TitleBarIcon); + + #endregion + + #region Public Properties + + public static Icon DefaultTitleBarIcon { get; set; } + public static uint MaxNumberOfCharactersPerLine { get; set; } + public const uint DefaultNumberOfCharactersPerLine = 62; + + #endregion + + #region Public Header Properties + + public static bool ShowHeader { get; set; } + public static string MsgBox_ErrorHeader { get; set; } + public static string MsgBox_FatalErrorHeader { get; set; } + public static string MsgBox_WarningHeader { get; set; } + public static string MsgBox_InfoHeader { get; set; } + + #endregion + + #region Public Footer Properties + + public static bool ShowFooter { get; set; } + public static string MsgBox_ErrorFooter { get; set; } + public static string MsgBox_FatalErrorFooter { get; set; } + public static string MsgBox_WarningFooter { get; set; } + public static string MsgBox_InfoFooter { get; set; } + + #endregion + + #region Public Title Header Properties + + public static bool ShowTitleHeader { get; set; } + public static string MsgBox_ErrorTitleHeader { get; set; } + public static string MsgBox_FatalErrorTitleHeader { get; set; } + public static string MsgBox_WarningTitleHeader { get; set; } + public static string MsgBox_InfoTitleHeader { get; set; } + + #endregion + + #region Construction + + static MsgBox() + { + DefaultTitleBarIcon = null; + MaxNumberOfCharactersPerLine = DefaultNumberOfCharactersPerLine; + + // Header Init + MsgBox_FatalErrorHeader = String.Empty; + MsgBox_ErrorHeader = String.Empty; + MsgBox_WarningHeader = String.Empty; + MsgBox_InfoHeader = String.Empty; + ShowHeader = true; + + // Footer Init + MsgBox_FatalErrorFooter = String.Empty; + MsgBox_ErrorFooter = String.Empty; + MsgBox_WarningFooter = String.Empty; + MsgBox_InfoFooter = String.Empty; + ShowFooter = true; + + // Title Header Init + MsgBox_FatalErrorTitleHeader = String.Empty; + MsgBox_ErrorTitleHeader = String.Empty; + MsgBox_WarningTitleHeader = String.Empty; + MsgBox_InfoTitleHeader = String.Empty; + ShowTitleHeader = true; + } + + #endregion + + #region Public Static Methods + + /// + /// Shows a custom Message Box (centered to parent), with the DefaultTitleBarIcon + /// + public static DialogResult Show(WPFWin.Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons, MessageBoxIcon MsgBoxIcon) + { + if (parent != null) + { + WPFWinInterop.WindowInteropHelper interop = new WPFWinInterop.WindowInteropHelper(parent); + if(interop.Handle != IntPtr.Zero) + return Show(Watchdog.WatchdogLib.Win32.Convert.ConverthWndToIWin32Window(interop.Handle), Title, Text, MsgBoxButtons, MsgBoxIcon, DefaultTitleBarIcon); + } + return Show(Functions.GetDestopWindow(), Title, Text, MsgBoxButtons, MsgBoxIcon, DefaultTitleBarIcon); + } + + /// + /// Shows a custom Message Box (centered to parent), with the DefaultTitleBarIcon + /// + public static DialogResult Show(IWin32Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons, MessageBoxIcon MsgBoxIcon) + { + return Show(parent, Title, Text, MsgBoxButtons, MsgBoxIcon, DefaultTitleBarIcon); + } + + /// + /// Shows a custom Message Box (centered to Desktop), with the DefaultTitleBarIcon + /// + public static DialogResult Show(String Title, String Text, MessageBoxButtons MsgBoxButtons, MessageBoxIcon MsgBoxIcon) + { + return Show(Functions.GetDestopWindow(), Title, Text, MsgBoxButtons, MsgBoxIcon, DefaultTitleBarIcon); + } + + /// + /// *Main MessageBox Show Function* allows you to center the Message Box to the Parent + /// + /// Result of Dialog + public static DialogResult Show(IWin32Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons, MessageBoxIcon MsgBoxIcon, Icon TitleBarIcon) + { + ISynchronizeInvoke InvokeObject = null; + if (parent != null && parent is ISynchronizeInvoke) + InvokeObject = (ISynchronizeInvoke)parent; + + // Invoke if we need to * Make MessageBoxes generally Thread-safe * + if ((InvokeObject != null) && InvokeObject.InvokeRequired) + { + DialogResult result = (DialogResult)InvokeObject.Invoke(new ShowMsgBoxDelegate(MsgBox.Show), new object[] { parent, Title, Text, MsgBoxButtons, MsgBoxIcon, TitleBarIcon }); + return result; + } + else + { + return MsgBox.ShowInternal(parent, Text, Title, MsgBoxButtons, MsgBoxIcon, TitleBarIcon); + } + } + + #endregion + + #region Public Static Methods Extended + + /// + /// Shows Warning MessageBox + /// + //public static DialogResult ShowWarning(IWin32Window parent, String Text, String Title = "", MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + public static DialogResult ShowWarning(IWin32Window parent, String Text, String Title, MessageBoxButtons MsgBoxButtons) + { + return Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Exclamation); + } + + /// + /// Shows Warning MessageBox + /// + //public static DialogResult ShowWarning(String Text, String Title = "", MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + public static DialogResult ShowWarning(String Text, String Title, MessageBoxButtons MsgBoxButtons) + { + return Show(Title, Text, MsgBoxButtons, MessageBoxIcon.Exclamation); + } + + /// + /// Shows Fatal Error MessageBox + /// + //public static DialogResult ShowFatalError(IWin32Window parent, String Text, String Title = "", MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + public static DialogResult ShowFatalError(IWin32Window parent, String Text, String Title, MessageBoxButtons MsgBoxButtons) + { + _ShowFatal = true; + DialogResult dr = Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Error); + _ShowFatal = false; + return dr; + } + + /// + /// Shows Fatal Error MessageBox + /// + //public static DialogResult ShowFatalError(String Text, String Title = "", MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + public static DialogResult ShowFatalError(String Text, String Title, MessageBoxButtons MsgBoxButtons) + { + _ShowFatal = true; + DialogResult dr = Show(Title, Text, MsgBoxButtons, MessageBoxIcon.Error); + _ShowFatal = false; + return dr; + } + + /// + /// Shows Error MessageBox + /// + //public static DialogResult ShowError(IWin32Window parent, String Text, String Title = "", MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + public static DialogResult ShowError(IWin32Window parent, String Text, String Title, MessageBoxButtons MsgBoxButtons) + { + return Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Error); + } + + /// + /// Shows Error MessageBox + /// + //public static DialogResult ShowError(String Text, String Title = "", MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + public static DialogResult ShowError(String Text, String Title, MessageBoxButtons MsgBoxButtons) + { + return Show(Title, Text, MsgBoxButtons, MessageBoxIcon.Error); + } + + /// + /// Shows Information MessageBox + /// + //public static DialogResult ShowInfo(IWin32Window parent, String Text, String Title = "", MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + public static DialogResult ShowInfo(IWin32Window parent, String Text, String Title, MessageBoxButtons MsgBoxButtons) + { + return Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Information); + } + + /// + /// Shows Information MessageBox + /// + //public static DialogResult ShowInfo(String Text, String Title = "", MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + public static DialogResult ShowInfo(String Text, String Title, MessageBoxButtons MsgBoxButtons) + { + return Show(Title, Text, MsgBoxButtons, MessageBoxIcon.Information); + } + + /// + /// Shows Default MessageBox + /// + //public static DialogResult ShowDefault(IWin32Window parent, String Text, String Title = "", MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + public static DialogResult ShowDefault(IWin32Window parent, String Text, String Title, MessageBoxButtons MsgBoxButtons) + { + return Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.None); + } + + /// + /// Shows Default MessageBox + /// + //public static DialogResult ShowDefault(String Text, String Title = "", MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + public static DialogResult ShowDefault(String Text, String Title, MessageBoxButtons MsgBoxButtons) + { + return Show(Title, Text, MsgBoxButtons, MessageBoxIcon.None); + } + + #endregion + + #region Private Methods + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static DialogResult ShowInternal(IWin32Window owner, string msg, string title, MessageBoxButtons btns, MessageBoxIcon icon, Icon TitleBarIcon) + { + // Create a callback delegate + _hookProcDelegate = new User32.WindowsHookProc(HookCallback); + + #region Header Action + if (ShowHeader) + { + if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_FatalErrorHeader) && _ShowFatal) + msg = MsgBox_FatalErrorHeader + msg; + else if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_ErrorHeader) && !_ShowFatal) + msg = MsgBox_ErrorHeader + msg; + else if (icon == MessageBoxIcon.Exclamation && !String.IsNullOrEmpty(MsgBox_WarningHeader)) + msg = MsgBox_WarningHeader + msg; + else if (icon == MessageBoxIcon.Information && !String.IsNullOrEmpty(MsgBox_InfoHeader)) + msg = MsgBox_InfoHeader + msg; + } + #endregion + + #region Footer Action + if (ShowFooter) + { + if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_FatalErrorFooter) && _ShowFatal) + msg = msg + MsgBox_FatalErrorFooter; + else if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_ErrorFooter) && !_ShowFatal) + msg = msg + MsgBox_ErrorFooter; + else if (icon == MessageBoxIcon.Exclamation && !String.IsNullOrEmpty(MsgBox_WarningFooter)) + msg = msg + MsgBox_WarningFooter; + else if (icon == MessageBoxIcon.Information && !String.IsNullOrEmpty(MsgBox_InfoFooter)) + msg = msg + MsgBox_InfoFooter; + } + #endregion + + #region Title Header + if (ShowTitleHeader) + { + if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_FatalErrorTitleHeader) && _ShowFatal) + title = MsgBox_FatalErrorTitleHeader + ((!String.IsNullOrEmpty(title)) ? (" (" + title + ")") : ""); + else if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_ErrorTitleHeader) && !_ShowFatal) + title = MsgBox_ErrorTitleHeader + ((!String.IsNullOrEmpty(title)) ? (" (" + title + ")") : ""); + else if (icon == MessageBoxIcon.Exclamation && !String.IsNullOrEmpty(MsgBox_WarningTitleHeader)) + title = MsgBox_WarningTitleHeader + ((!String.IsNullOrEmpty(title)) ? (" (" + title + ")") : ""); + else if (icon == MessageBoxIcon.Information && !String.IsNullOrEmpty(MsgBox_InfoTitleHeader)) + title = MsgBox_InfoTitleHeader + ((!String.IsNullOrEmpty(title)) ? (" (" + title + ")") : ""); + } + #endregion + + #region Text Padding + + // Stripe last \n, if exists + if (!String.IsNullOrEmpty(msg) && (msg.Length > 0) && (msg[msg.Length - 1] == '\n')) + msg = msg.Remove(msg.Length - 1); + + // Make sure the text looks good, by using padding + if (!String.IsNullOrEmpty(msg) && (msg.Length > 0) && (msg.Length < MaxNumberOfCharactersPerLine)) + { + string[] lines = msg.Split('\n'); + StringBuilder sb = new StringBuilder(); + foreach (string line in lines) + { + sb.Append(line.PadRight((int)MaxNumberOfCharactersPerLine)); + sb.Append("\n"); + } + msg = sb.ToString(); + } + else if (!String.IsNullOrEmpty(msg) && (msg.Length > 0) && (msg.Length > MaxNumberOfCharactersPerLine)) + { + // Incredible and amazing Padding code + string[] lines = msg.Split('\n'); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < lines.Length; ++i) + { + if (lines[i].Length < MaxNumberOfCharactersPerLine) + { + sb.Append(lines[i].PadRight((int)MaxNumberOfCharactersPerLine)); + sb.Append("\n"); + } + else if (lines[i].Length > MaxNumberOfCharactersPerLine) + { + // truncate + for (int j = 0; j < lines[i].Length; j = j + (int)MaxNumberOfCharactersPerLine) + { + string strSub = lines[i].Substring(j, ((lines[i].Length - j) > (int)MaxNumberOfCharactersPerLine) ? (int)MaxNumberOfCharactersPerLine : (lines[i].Length - j)); + if (strSub.Length == (int)MaxNumberOfCharactersPerLine) + { + sb.Append(strSub); + sb.Append("\n"); + } + else + { + sb.Append(strSub.PadRight((int)MaxNumberOfCharactersPerLine)); + sb.Append("\n"); + } + } + } + else + { + sb.Append(lines[i]); + sb.Append("\n"); + } + } + + // Write nicely formatted Message out + msg = sb.ToString(); + } + else + { + // do nothing, string is miracioulsy exactly correct + } + #endregion + + // Remember the title & message that we'll look for. + // The hook sees *all* windows, so we need to make sure we operate on the right one. + _msg = msg; + _title = title; + + // if Owner is the Desktop Window + _IsDesktopOwner = (owner.Handle == Functions.GetDestopWindow().Handle); + + // Icon could not be present + if (TitleBarIcon != null) + _hIcon = TitleBarIcon.ToBitmap().GetHicon(); + + // Set the hook. + // Suppress "GetCurrentThreadId() is deprecated" warning. + // It's documented that Thread.ManagedThreadId doesn't work with SetWindowsHookEx() +#pragma warning disable 0618 + _hHook = User32.SetWindowsHookEx(Definitions.WH_CBT, _hookProcDelegate, IntPtr.Zero, AppDomain.GetCurrentThreadId()); +#pragma warning restore 0618 + + // Pop a standard MessageBox. The hook will center it. + DialogResult rslt = DialogResult.None; + if (_IsDesktopOwner) + rslt = MessageBox.Show(_msg, _title, btns, icon); + else + rslt = MessageBox.Show(owner, _msg, _title, btns, icon); + + // Release hook, clean up (may have already occurred) + Unhook(); + + return rslt; + } + + private static void Unhook() + { + User32.UnhookWindowsHookEx(_hHook); + _hHook = 0; + _hookProcDelegate = null; + _msg = null; + _title = null; + } + + private static int HookCallback(int code, IntPtr wParam, IntPtr lParam) + { + int hHook = _hHook; // Local copy for CallNextHookEx() JIC we release _hHook + + // Look for HCBT_ACTIVATE, *not* HCBT_CREATEWND: + // child controls haven't yet been created upon HCBT_CREATEWND. + if (code == Definitions.HCBT_ACTIVATE) + { + string cls = Functions.GetWindowClassName(wParam); + if (cls == "#32770") // MessageBoxes are Dialog boxes + { + string title = Functions.GetWindowText(wParam); + string msg = Functions.GetDlgItemText(wParam, 0xFFFF); // -1 aka IDC_STATIC + if ((title == _title) && (msg == _msg)) + { + // Only Center the Window, if the Owner is NOT the Desktop + if (!_IsDesktopOwner) + CenterWindowOnParent(wParam); + + Unhook(); // Release hook - we've done what we needed + + // Now we also want to set the Icon on the Dialog + if (_hIcon != IntPtr.Zero) + { + User32.SendMessage(wParam, (int)Definitions.WM.WM_SETICON, (IntPtr)1, _hIcon); + User32.SendMessage(wParam, (int)Definitions.WM.WM_SETICON, (IntPtr)0, _hIcon); + } + } + } + } + return User32.CallNextHookEx(hHook, code, wParam, lParam); + } + + // Boilerplate window-centering code. + // Split out of HookCallback() for clarity. + private static void CenterWindowOnParent(IntPtr hChildWnd) + { + // Get child (MessageBox) size + Structures.RECT rcChild = new Structures.RECT(); + User32.GetWindowRect(hChildWnd, out rcChild); + int cxChild = rcChild.right - rcChild.left; + int cyChild = rcChild.bottom - rcChild.top; + + // Get parent (Form) size & location + IntPtr hParent = User32.GetParent(hChildWnd); + Structures.RECT rcParent = new Structures.RECT(); + User32.GetWindowRect(hParent, out rcParent); + int cxParent = rcParent.right - rcParent.left; + int cyParent = rcParent.bottom - rcParent.top; + + // Center the MessageBox on the Form + int x = rcParent.left + (cxParent - cxChild) / 2; + int y = rcParent.top + (cyParent - cyChild) / 2; + uint uFlags = 0x15; // SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE; + User32.SetWindowPos(hChildWnd, IntPtr.Zero, x, y, 0, 0, uFlags); + } + + #endregion + } +} diff --git a/@integrate/README.txt b/@integrate/README.txt new file mode 100644 index 0000000..9f9479d --- /dev/null +++ b/@integrate/README.txt @@ -0,0 +1,12 @@ +Things to go over + +File->Logging.cs +Process->PStarter.cs +Thread->SingleThreadTimer.cs (new) +Watchdog->SysTray.cs +Watchdog->MsgBox.cs +Watchdog->Timer.cs +Watchdog->Installer.cs (new) +Watchdog->CHMFile.cs (new) + + diff --git a/@integrate/ResxHelper.cs b/@integrate/ResxHelper.cs new file mode 100644 index 0000000..3c31973 --- /dev/null +++ b/@integrate/ResxHelper.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Resources; +using System.Reflection; +using System.IO; +using Watchdog.WatchdogLib.Assembly; + +namespace Watchdog +{ + // ResxTypes + public enum Resx + { + AppResx, + } + + #region ResxHelper Wrapper Classes + + /// + /// AppResx Wrapper String Resource Value Functions + /// + public static class AppResx + { + public static string GetString(string name) + { + return ResxHelper.GetStringValue(Resx.AppResx, name); + } + public static string GetStringFormated(string name, params object[] args) + { + return ResxHelper.GetStringValueFormated(Resx.AppResx, name, args); + } + } + + #endregion + + /// + /// Allows External Callers To Quickly gain access to the Resources Contained in this Assembly + /// + public static class ResxHelper + { + /// + /// Private static Dictionary Map of Resource Managers + /// + private static Dictionary _ResourceMap = new Dictionary(); + + /// + /// Static Constructor, iterates through the enumerations and loads resourceManagers internally + /// + static ResxHelper() + { + //string[] resources = Assembly.GetExecutingAssembly().GetManifestResourceNames(); + //~we shouldn't be doing this on filename (filename could change) + //string curAsmName = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().ManifestModule.Name); + string curAsmName = AssemblyW.GetAssemblyName(AssemblyW.AssemblyST.Entry); + + // Create a ResourceManager for each Resource Type in this Assembly + foreach (string ResxName in Enum.GetNames(typeof(Resx))) + _ResourceMap.Add((Resx)Enum.Parse(typeof(Resx), ResxName), new ResourceManager((curAsmName + "." + ResxName), Assembly.GetExecutingAssembly())); + } + + #region String Resource Value Functions + + /// + /// Returns the Value of a String Resource via GetString() + /// + /// Reource Type to Access + /// Name of String Resource to get + /// the value of the resource, or "" if not found/Error Occured + public static string GetStringValue(Resx resx, string Name) + { + try + { + if (!String.IsNullOrEmpty(Name)) + { + string Value = PreserverFormating(_ResourceMap[resx].GetString(Name)); + return Value; + } + } + catch (Exception) { /* ignore */ } + return String.Empty; + } + + /// + /// Returns a Formated String Value of a String Resource via GetString() + /// + /// Reource Type to Access + /// Name of String Resource to get + /// Arguments to pass into String.Format() + /// the value of the resource, or "" if not found/Error Occured + public static string GetStringValueFormated(Resx resx, string Name, params object[] args) + { + String retVal = GetStringValue(resx, Name); + if (!String.IsNullOrEmpty(retVal)) + return String.Format(retVal, args); + else + return String.Empty; + } + + /// + /// we want to preserver formating using '\' characters that are in the resource + /// + /// a string value retrieved from the resource + /// a string that preserves formatting + private static string PreserverFormating(string Value) + { + if (!String.IsNullOrEmpty(Value)) + { + Value = Value.Replace("\\N", "\n"); + Value = Value.Replace("\\n", "\n"); + Value = Value.Replace("\\T", "\t"); + Value = Value.Replace("\\t", "\t"); + return Value; + } + return String.Empty; + } + + #endregion + } +} diff --git a/@integrate/Settings.cs b/@integrate/Settings.cs new file mode 100644 index 0000000..f0e3135 --- /dev/null +++ b/@integrate/Settings.cs @@ -0,0 +1,1057 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Watchdog.WatchdogLib.Registry; +using Watchdog.WatchdogLib.Assembly; +using Watchdog.WatchdogLib.Monitor; +using Watchdog.WatchdogLib.Process; +using System.Diagnostics; +using WatchdogLib.Tools; + +namespace Watchdog +{ + internal class Settings + { + /// + /// Critical Section + /// + private object _lock = new object(); + + #region Construction + + /// + /// Construction + /// + internal Settings() + { + } + + #endregion + + #region Scheduler N' Scheduler Backup Settings + + /// + /// Minimum Minute Interval for Schedulers + /// + public const uint MINIMUM_MINUTE_INTERVAL_SCHEDULERS = 10; + + /// + /// True if there are Settings specified for the Scheduler + /// + internal bool SchedulerSettingsExist + { + get + { + DateTime dtStart = DateTime.MinValue; + DateTime dtLastRun = DateTime.MinValue; + uint nHoursOrMinutes = 0; + bool bIsHours = true; + return GetSchedulerSettings(out dtStart, out dtLastRun, out nHoursOrMinutes, out bIsHours); + } + } + + /// + /// True if there are Settings specified for the Scheduler Backup + /// + internal bool SchedulerBackupSettingsExist + { + get + { + DateTime dtStart = DateTime.MinValue; + DateTime dtLastRun = DateTime.MinValue; + uint nHoursOrMinutes = 0; + bool bIsHours = true; + return GetSchedulerBackupSettings(out dtStart, out dtLastRun, out nHoursOrMinutes, out bIsHours); + } + } + + /// + /// Use this to retrieve the Scheduler Settings + /// + /// Start Time + /// Last Run Time + /// number of Hours or Minutes + /// is hours + /// Returns true if there is Scheduler Information in the Settings, also returns the settings, false otherwise + internal bool GetSchedulerSettings(out DateTime dtStart, out DateTime dtLastRun, out uint nHoursOrMinutes, out bool bIsHours) + { + lock (_lock) + { + dtStart = DateTime.MinValue; + dtLastRun = DateTime.MinValue; + nHoursOrMinutes = 0; + bIsHours = true; + + if (uint.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_RepeatEveryNHoursOrMinutes, "0"), out nHoursOrMinutes) && + DateTime.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_StartDateTime, DateTime.MinValue.ToString()), out dtStart) && + nHoursOrMinutes > 0 && + dtStart != DateTime.MinValue + ) + { + // Get the Last time this scheduler was run + dtLastRun = DateTime.MinValue; + DateTime.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_LastRun_DateTime, DateTime.MinValue.ToString()), out dtLastRun); + + // Are we on an hourly or minute schedule? + bIsHours = App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_RepeatIsHour, true); + + // Minimum Interval for Minutes + if (!bIsHours && nHoursOrMinutes < MINIMUM_MINUTE_INTERVAL_SCHEDULERS) + nHoursOrMinutes = MINIMUM_MINUTE_INTERVAL_SCHEDULERS; + + // return only true if nHoursOrMinutes is bigger 0 and dtStart is NOT equal to MinVal + return true; + } + + return false; + } + } + + /// + /// Use this to retrieve the Scheduler Backup Settings + /// + /// Start Time + /// Last Run Time + /// number of Hours or Minutes + /// is hours + /// Returns true if there is Scheduler Backup Information in the Settings, also returns the settings, false otherwise + internal bool GetSchedulerBackupSettings(out DateTime dtStart, out DateTime dtLastRun, out uint nHoursOrMinutes, out bool bIsHours) + { + lock (_lock) + { + dtStart = DateTime.MinValue; + dtLastRun = DateTime.MinValue; + nHoursOrMinutes = 0; + bIsHours = true; + + if (uint.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_ForDurationNHoursOrMinutes, "0"), out nHoursOrMinutes) && + DateTime.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_StartDateTime, DateTime.MinValue.ToString()), out dtStart) && + nHoursOrMinutes > 0 && + dtStart != DateTime.MinValue + ) + { + // Get the Last time this scheduler was run + dtLastRun = DateTime.MinValue; + DateTime.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_LastRun_DateTime, DateTime.MinValue.ToString()), out dtLastRun); + + // Are we on an hourly or minute schedule? + bIsHours = App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_DurationIsHour, true); + + // Minimum Interval for Minutes + if (!bIsHours && nHoursOrMinutes < MINIMUM_MINUTE_INTERVAL_SCHEDULERS) + nHoursOrMinutes = MINIMUM_MINUTE_INTERVAL_SCHEDULERS; + + // return only true if nHoursOrMinutes is bigger 0 and dtStart is NOT equal to MinVal + return true; + } + + return false; + } + } + + /// + /// Set/Get the Scheduler Start DT + /// + internal DateTime SchedulerStartDateTime + { + get + { + string strDateTime = App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_StartDateTime, DateTime.MinValue.ToString()); + DateTime dt; + if (DateTime.TryParse(strDateTime, out dt) && dt != DateTime.MinValue) + return dt; + else + return DateTime.Now; + } + set + { + if (value != DateTime.MinValue) + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_StartDateTime, value.ToString()); + } + } + + /// + /// Set/Get the Scheduler Backup Start DT + /// + internal DateTime SchedulerBackupStartDateTime + { + get + { + string strDateTime = App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_StartDateTime, DateTime.MinValue.ToString()); + DateTime dt; + if (DateTime.TryParse(strDateTime, out dt) && dt != DateTime.MinValue) + return dt; + else + return DateTime.Now; + } + set + { + if (value != DateTime.MinValue) + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_StartDateTime, value.ToString()); + } + } + + /// + /// Set/Get the Scheduler Repeat Hours or Minutes + /// + internal uint SchedulerRepeatEveryHoursOrMinutes + { + get + { + uint nRepeatEveryNHoursOrMinutes = 0; + if (uint.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_RepeatEveryNHoursOrMinutes, "0"), out nRepeatEveryNHoursOrMinutes) && (nRepeatEveryNHoursOrMinutes >= 1 && nRepeatEveryNHoursOrMinutes <= 8760)) + { + if ((nRepeatEveryNHoursOrMinutes > 0) && (nRepeatEveryNHoursOrMinutes < MINIMUM_MINUTE_INTERVAL_SCHEDULERS) && !SchedulerRepeatIsHour) + return MINIMUM_MINUTE_INTERVAL_SCHEDULERS; + else + return nRepeatEveryNHoursOrMinutes; + } + else + { + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_RepeatEveryNHoursOrMinutes, "0"); + return 0; + } + } + set + { + if (value >= 0 && value <= 8760) + { + if ((value > 0) && (value < MINIMUM_MINUTE_INTERVAL_SCHEDULERS) && !SchedulerRepeatIsHour) + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_RepeatEveryNHoursOrMinutes, MINIMUM_MINUTE_INTERVAL_SCHEDULERS.ToString()); + else + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_RepeatEveryNHoursOrMinutes, value.ToString()); + } + } + } + + /// + /// Set/Get the Scheduler Backup Duration Hours or Minutes + /// + internal uint SchedulerBackupForDurationHoursOrMinutes + { + get + { + uint nForDurationNHoursOrMinutes = 0; + if (uint.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_ForDurationNHoursOrMinutes, "0"), out nForDurationNHoursOrMinutes) && (nForDurationNHoursOrMinutes >= 1 && nForDurationNHoursOrMinutes <= 8760)) + { + if ((nForDurationNHoursOrMinutes > 0) && (nForDurationNHoursOrMinutes < MINIMUM_MINUTE_INTERVAL_SCHEDULERS) && !SchedulerBackupDurationIsHour) + return MINIMUM_MINUTE_INTERVAL_SCHEDULERS; + else + return nForDurationNHoursOrMinutes; + } + else + { + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_ForDurationNHoursOrMinutes, "0"); + return 0; + } + } + set + { + if (value >= 0 && value <= 8760) + { + if ((value > 0) && (value < MINIMUM_MINUTE_INTERVAL_SCHEDULERS) && !SchedulerBackupDurationIsHour) + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_ForDurationNHoursOrMinutes, MINIMUM_MINUTE_INTERVAL_SCHEDULERS.ToString()); + else + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_ForDurationNHoursOrMinutes, value.ToString()); + } + } + } + + /// + /// Set/Get the Scheduler Repeat Is Hour Flag + /// + internal bool SchedulerRepeatIsHour + { + get + { + return App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_RepeatIsHour, true); + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_RepeatIsHour, value); + } + } + + /// + /// Set/Get the Scheduler Backup Duration Is Hour Flag + /// + internal bool SchedulerBackupDurationIsHour + { + get + { + return App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_DurationIsHour, true); + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_DurationIsHour, value); + } + } + + /// + /// Boolean Flag indicating whether the Scheduler Restarts Services + /// + internal bool SchedulerRestartsServices + { + get + { + return App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Scheduler_Restarts_Services, false); + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Scheduler_Restarts_Services, value); + } + } + + /// + /// DT stamp when the Scheduler was last run + /// + internal DateTime LastSchedulerRun + { + get + { + // Get the Last time this scheduler was run + DateTime dtLastRun = DateTime.MinValue; + DateTime.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_LastRun_DateTime, DateTime.MinValue.ToString()), out dtLastRun); + return dtLastRun; + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Sheduler_LastRun_DateTime, value.ToString()); + } + + } + + /// + /// DT stamp when the Scheduler Backup was last run + /// + internal DateTime LastSchedulerBackupRun + { + get + { + // Get the Last time this scheduler was run + DateTime dtLastRun = DateTime.MinValue; + DateTime.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_LastRun_DateTime, DateTime.MinValue.ToString()), out dtLastRun); + return dtLastRun; + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_LastRun_DateTime, value.ToString()); + } + + } + + /// + /// Boolean Flag indicating whether the Scheduler Backup Stops Instead of Pauses + /// + internal bool SchedulerBackupStopInsteadOfPause + { + get + { + return App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_StopInsteadOfPause, true); + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_StopInsteadOfPause, value); + } + } + + /// + /// Boolean Flag indicating whether the Scheduler Backup Stops Servies + /// + internal bool SchedulerBackupStopServices + { + get + { + return App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_StopServices, true); + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__ShedulerBackup_StopServices, value); + } + } + + #endregion + + #region Program Settings + + /// + /// Boolean flag indicates whether upon program start it should start monitoring right away + /// + internal bool StartMonitorUponStart + { + get + { + return App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Monitor_On_Start, true); + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Monitor_On_Start, value); + } + } + + /// + /// Indicates the max failure count that can occur per process/service within a given MaxFailureCountHour Timespan + /// * Important Setting for Process Monitor * Value can be between 1 and 99 + /// + internal uint MaxFailureCount + { + get + { + // Re-read MaxFailureCount Setting * Default is specified in AppResx * + uint nMaxFailureCount = uint.Parse(AppResx.GetString("DEFAULT_VALUE_MAX_PROCESS_START_FAILURE_COUNT")); + bool bCanParseIni = uint.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Max_Fail_Count, AppResx.GetString("DEFAULT_VALUE_MAX_PROCESS_START_FAILURE_COUNT")), out nMaxFailureCount); + if (!bCanParseIni) + App.log.Error(AppResx.GetStringFormated("ERROR_INI_SETTINGS_PARSE", App.Ini_Setting.MonitorSettings__Max_Fail_Count.ToString())); + if (nMaxFailureCount < 1) + nMaxFailureCount = 1; + else if (nMaxFailureCount > 99) + nMaxFailureCount = 99; + return nMaxFailureCount; + } + set + { + if (value >= 1 && value <= 99) + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Max_Fail_Count, value.ToString()); + } + } + + /// + /// Indicates the Hour Timespan in which MaxFailureCount can occur + /// * Important Setting for Process Monitor * Value can be between 1 and 8760 + /// + internal uint MaxFailureCountHour + { + get + { + // Re-read MaxFailureCountHour Setting * Default is specified in AppResx + uint nMaxFailureCountHour = uint.Parse(AppResx.GetString("DEFAULT_VALUE_MAX_PROCESS_START_FAILURE_COUNT_HOUR")); + bool bCanParseIni = uint.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Max_Fail_Count_Per_Hour, AppResx.GetString("DEFAULT_VALUE_MAX_PROCESS_START_FAILURE_COUNT_HOUR")), out nMaxFailureCountHour); + if (!bCanParseIni) + App.log.Error(AppResx.GetStringFormated("ERROR_INI_SETTINGS_PARSE", App.Ini_Setting.MonitorSettings__Max_Fail_Count_Per_Hour.ToString())); + if (nMaxFailureCountHour < 1) + nMaxFailureCountHour = 1; + else if (nMaxFailureCountHour > 8760) + nMaxFailureCountHour = 8760; + return nMaxFailureCountHour; + } + set + { + if (value >= 1 && value <= 8760) + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Max_Fail_Count_Per_Hour, value.ToString()); + } + } + + /// + /// Get/Set the Last Program Version in the Registry * Used to check if an upgrade occured * + /// + internal string LastProgramVersion + { + get + { + string LastVersion = RegKey.GetKey(App.APPLICATION_NAME_SHORT, "LastProgramVersion", String.Empty); + return LastVersion; + } + set + { + RegKey.SetKey(App.APPLICATION_NAME_SHORT, "LastProgramVersion", value); + } + } + + #endregion + + #region Email Settings + + /// + /// Boolean flag indicates whether Email Notifications are enabled + /// + internal bool EmailNotificationEnabled + { + get + { + return App.inifile.GetKeyValue(App.Ini_Setting.EmailSettings__EnableEmailNotifications, false); + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__EnableEmailNotifications, value); + } + } + + /// + /// True if the settings for Email are valid + /// + internal bool EmailSettingsValid + { + get + { + // Check reg + bool NotValid = String.IsNullOrEmpty(EmailStmpServer) || String.IsNullOrEmpty(EmailSenderEmail) || + String.IsNullOrEmpty(EmailReceiverEmail); + if (NotValid) + return false; + + // Check auth + if (EmailStmpServerRequiresAuth) + NotValid = String.IsNullOrEmpty(EmailSmtpUsername) || String.IsNullOrEmpty(EmailSmtpPassword); + return !NotValid; + } + } + + /// + /// Boolean flag indicates whether Email Smtp Server Requires Authentication + /// + internal bool EmailStmpServerRequiresAuth + { + get + { + return App.inifile.GetKeyValue(App.Ini_Setting.EmailSettings__SmtpRequiresAuth, false); + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__SmtpRequiresAuth, value); + } + } + + /// + /// Boolean flag indicates whether Email Smtp Server Requires SSL + /// + internal bool EmailStmpServerRequiresSSL + { + get + { + return App.inifile.GetKeyValue(App.Ini_Setting.EmailSettings__SmtpRequiresSSL, false); + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__SmtpRequiresSSL, value); + } + } + + /// + /// Email Smtp Server + /// + internal string EmailStmpServer + { + get + { + string strFound = App.inifile.GetKeyValue(App.Ini_Setting.EmailSettings__SmtpServer, ""); + if (!String.IsNullOrEmpty(strFound) && strFound.Contains('.')) + return strFound; + else + return String.Empty; + } + set + { + if(!String.IsNullOrEmpty(value) && value.Contains('.')) + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__SmtpServer, value); + } + } + + /// + /// Email Sender Email + /// + internal string EmailSenderEmail + { + get + { + string strFound = App.inifile.GetKeyValue(App.Ini_Setting.EmailSettings__SenderEmail, ""); + if (!String.IsNullOrEmpty(strFound) && strFound.Contains('.') && strFound.Contains('@')) + return strFound; + else + return String.Empty; + } + set + { + if (!String.IsNullOrEmpty(value) && value.Contains('.') && value.Contains('@')) + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__SenderEmail, value); + } + } + + /// + /// Email Receiver Email + /// + internal string EmailReceiverEmail + { + get + { + string strFound = App.inifile.GetKeyValue(App.Ini_Setting.EmailSettings__ReveiverEmail, ""); + if (!String.IsNullOrEmpty(strFound) && strFound.Contains('.') && strFound.Contains('@')) + return strFound; + else + return String.Empty; + } + set + { + if (!String.IsNullOrEmpty(value) && value.Contains('.') && value.Contains('@')) + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__ReveiverEmail, value); + } + } + + /// + /// Email Smtp Port * Default is 25 * + /// + internal uint EmailSmtpPort + { + get + { + uint nPort = 25; + bool bCanParseIni = uint.TryParse(App.inifile.GetKeyValue(App.Ini_Setting.EmailSettings__SmtpPort, "25"), out nPort); + if (!bCanParseIni) + App.log.Error(AppResx.GetStringFormated("ERROR_INI_SETTINGS_PARSE", App.Ini_Setting.EmailSettings__SmtpPort.ToString())); + if (nPort < 1) + nPort = 1; + else if (nPort > 65536) + nPort = 65536; + return nPort; + } + set + { + if (value >= 1 && value <= 65536) + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__SmtpPort, value.ToString()); + } + } + + /// + /// Email Smtp Username + /// + internal string EmailSmtpUsername + { + get + { + string strFound = App.inifile.GetKeyValue(App.Ini_Setting.EmailSettings__SmtpUsername, ""); + if (!String.IsNullOrEmpty(strFound)) + return strFound; + else + return String.Empty; + } + set + { + if (!String.IsNullOrEmpty(value)) + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__SmtpUsername, value); + else + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__SmtpUsername, ""); + } + } + + /// + /// Email Smtp Password + /// + internal string EmailSmtpPassword + { + get + { + string strFound = App.inifile.GetKeyValue(App.Ini_Setting.EmailSettings__SmtpPassword, ""); + if (!String.IsNullOrEmpty(strFound)) + return strFound; + else + return String.Empty; + } + set + { + if (!String.IsNullOrEmpty(value)) + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__SmtpPassword, value); + else + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__SmtpPassword, ""); + } + } + + /// + /// Email Custom Message Text + /// + internal string EmailCustomMessageText + { + get + { + string strFound = App.inifile.GetKeyValue(App.Ini_Setting.EmailSettings__Custom_Message_Text, ""); + if (!String.IsNullOrEmpty(strFound)) + return strFound; + else + return String.Empty; + } + set + { + if (!String.IsNullOrEmpty(value)) + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__Custom_Message_Text, value); + else + App.inifile.SetKeyValue(App.Ini_Setting.EmailSettings__Custom_Message_Text, ""); + } + } + + #endregion + + #region Windows Settings + + /// + /// Boolean Flag indicates whether this app should start with windows + /// + internal bool StartWithWindows + { + get + { + bool bStartWithWindows = App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Start_With_Windows, true); + bool bSetOk = true; + if (bStartWithWindows) + bSetOk = RegKey.SetKey("Microsoft\\Windows\\CurrentVersion\\Run", App.APPLICATION_NAME_SHORT, AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Entry)); + else + bSetOk = RegKey.DeleteKey("Microsoft\\Windows\\CurrentVersion\\Run", App.APPLICATION_NAME_SHORT); + if (!bSetOk) + App.log.Error("Failed to write to Microsoft\\Windows\\CurrentVersion\\Run. Check Permissions."); + + // * Special Circumstance * + if (bStartWithWindows) + DeleteOffendingHL7KeyIfApplicable(); + + return bStartWithWindows; + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Start_With_Windows, value); + bool bSetOk = true; + if (value) + bSetOk = RegKey.SetKey("Microsoft\\Windows\\CurrentVersion\\Run", App.APPLICATION_NAME_SHORT, AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Entry)); + else + bSetOk = RegKey.DeleteKey("Microsoft\\Windows\\CurrentVersion\\Run", App.APPLICATION_NAME_SHORT); + + // * Special Circumstance * + if (value) + DeleteOffendingHL7KeyIfApplicable(); + + if (!bSetOk) + App.log.Error("Failed to write to HKCU\\Microsoft\\Windows\\CurrentVersion\\Run. Check Permissions."); + } + } + + /// + /// Boolean Flag indicates whether LockWorkstation should be called upon CU login + /// + internal bool LockWorkstationWithWindows + { + get + { + bool bLockWorkstationWithWindows = App.inifile.GetKeyValue(App.Ini_Setting.WindowsSettings__LockWorkstation_With_Windows, false); + bool bSetOk = true; + if (bLockWorkstationWithWindows) + bSetOk = RegKey.SetKey("Microsoft\\Windows\\CurrentVersion\\Run", "ALockWorkstation", "rundll32.exe user32.dll LockWorkStation"); + else + bSetOk = RegKey.DeleteKey("Microsoft\\Windows\\CurrentVersion\\Run", "ALockWorkstation"); + if (!bSetOk) + App.log.Error("Failed to write to HKCU\\Microsoft\\Windows\\CurrentVersion\\Run. Check Permissions."); + return bLockWorkstationWithWindows; + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.WindowsSettings__LockWorkstation_With_Windows, value); + bool bSetOk = true; + if (value) + bSetOk = RegKey.SetKey("Microsoft\\Windows\\CurrentVersion\\Run", "ALockWorkstation", "rundll32.exe user32.dll LockWorkStation"); + else + bSetOk = RegKey.DeleteKey("Microsoft\\Windows\\CurrentVersion\\Run", "ALockWorkstation"); + if (!bSetOk) + App.log.Error("Failed to write to HKCU\\Microsoft\\Windows\\CurrentVersion\\Run. Check Permissions."); + } + } + + /// + /// Boolean Flag indicates whether Windows should automatically Login + /// + internal bool EnableAutomaticLoginWithWindows + { + get + { + bool bAutoLogin = App.inifile.GetKeyValue(App.Ini_Setting.WindowsSettings__AutoLogin_With_Windows, false); + bool bSetOk = true; + if (bAutoLogin) + bSetOk = RegKey.SetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "AutoAdminLogon", "1"); + else + bSetOk = RegKey.SetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "AutoAdminLogon", "0"); + if (!bSetOk) + App.log.Error("Failed to write to HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon. Check Permissions."); + return bAutoLogin; + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.WindowsSettings__AutoLogin_With_Windows, value); + bool bSetOk = true; + if (value) + bSetOk = RegKey.SetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "AutoAdminLogon", "1"); + else + bSetOk = RegKey.SetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "AutoAdminLogon", "0"); + if (!bSetOk) + App.log.Error("Failed to write to HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon. Check Permissions."); + } + } + + /// + /// AutoLogin Default Domain + /// + internal string AutomaticLoginDefaultDomain + { + get + { + return RegKey.GetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultDomainName", Environment.UserDomainName); + } + set + { + bool bSetOk = true; + if (!String.IsNullOrEmpty(value)) + bSetOk = RegKey.SetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultDomainName", value); + else + bSetOk = RegKey.SetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultDomainName", ""); + if (!bSetOk) + App.log.Error("Failed to write to HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon. Check Permissions."); + } + } + + /// + /// AutoLogin Default Username + /// + internal string AutomaticLoginDefaultUsername + { + get + { + return RegKey.GetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultUserName", Environment.UserName); + } + set + { + bool bSetOk = true; + if (!String.IsNullOrEmpty(value)) + bSetOk = RegKey.SetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultUserName", value); + else + bSetOk = RegKey.SetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultUserName", ""); + if (!bSetOk) + App.log.Error("Failed to write to HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon. Check Permissions."); + } + } + + /// + /// AutoLogin Default Password + /// + internal string AutomaticLoginDefaultPassword + { + get + { + return RegKey.GetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultPassword", ""); + } + set + { + bool bSetOk = true; + if (!String.IsNullOrEmpty(value)) + bSetOk = RegKey.SetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultPassword", value); + else + bSetOk = RegKey.SetKey(HKEYRoot.LocalMachine, "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", "DefaultPassword", ""); + if (!bSetOk) + App.log.Error("Failed to write to HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon. Check Permissions."); + } + } + + /// + /// Creates an Exe HardLink to the Application .exe at the Rooth Path + /// + internal bool CreateExeHardLinkInRootPath + { + get + { + //fsutil hardlink create c:\foo.txt c:\bar.txt + bool bCreateHardLinkInRootPath = App.inifile.GetKeyValue(App.Ini_Setting.WindowsSettings__CreateExeHardLink_In_RootPath, true); + String FileNameWithExtension = AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameWithExtension(AssemblyW.AssemblyST.Entry); + String RootPath = PathNaming.GetPathRoot(AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Entry)); + String FileNameFullPath = AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Entry); + if (bCreateHardLinkInRootPath && (String.Compare(FileNameFullPath, String.Format("{0}{1}", RootPath, FileNameWithExtension), true) != 0)) + { + Process.Start(PStartInfo.CreateCMDDosCommandProcess(String.Format("del {0}{1}", RootPath, FileNameWithExtension), false)); + Process.Start(PStartInfo.CreateCMDDosCommandProcess(String.Format("fsutil hardlink create {0}{1} \"{2}\"", RootPath, FileNameWithExtension, FileNameFullPath), false)); + } + else if (String.Compare(FileNameFullPath, String.Format("{0}{1}", RootPath, FileNameWithExtension), true) != 0) + { + Process.Start(PStartInfo.CreateCMDDosCommandProcess(String.Format("del {0}{1}", "", ""), false)); + } + return bCreateHardLinkInRootPath; + } + set + { + App.inifile.SetKeyValue(App.Ini_Setting.WindowsSettings__CreateExeHardLink_In_RootPath, value); + String FileNameWithExtension = AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameWithExtension(AssemblyW.AssemblyST.Entry); + String RootPath = PathNaming.GetPathRoot(AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Entry)); + String FileNameFullPath = AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Entry); + if (value && (String.Compare(FileNameFullPath, String.Format("{0}{1}", RootPath, FileNameWithExtension), true) != 0)) + { + Process.Start(PStartInfo.CreateCMDDosCommandProcess(String.Format("del {0}\\{1}", RootPath, FileNameWithExtension), false)); + Process.Start(PStartInfo.CreateCMDDosCommandProcess(String.Format("fsutil hardlink create {0}\\{1} \"{2}\"", RootPath, FileNameWithExtension, FileNameFullPath), false)); + } + else if (String.Compare(FileNameFullPath, String.Format("{0}{1}", RootPath, FileNameWithExtension), true) != 0) + { + Process.Start(PStartInfo.CreateCMDDosCommandProcess(String.Format("del {0}\\{1}", "", ""), false)); + } + } + } + + #endregion + + #region Prefill Settings + + /// + /// Prefill's the default exes for the Process Monitor (GUI Prefill Feature) + /// + internal string DefaultExeRunningFilter + { + get + { + return App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Default_Exe_Running_Filter, AppResx.GetString("DEFAULT_EXE_RUNNING_FILTER")); + } + set + { + if(value != null) + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Default_Exe_Running_Filter, value); + } + } + + /// + /// Prefill's the default services for the Service Monitor (GUI Prefill Feature) + /// + internal string DefaultServicesNamesRunningFilter + { + get + { + return App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Default_Services_Names_Running_Filter, AppResx.GetString("DEFAULT_SERVICE_NAMES_RUNNING_FILTER")); + } + set + { + if(value != null) + App.inifile.SetKeyValue(App.Ini_Setting.MonitorSettings__Default_Services_Names_Running_Filter, value); + } + } + + #endregion + + #region Window Settings + + /// + /// Retrieve the Top N' Left for the Settings Window + /// + /// Top Double + /// Left Double + internal void GetWindowSetting_TopLeft_SettingsWindow(ref double Top, ref double Left) + { + // Load the Window at the correct position + try + { + string TopLeft = App.inifile.GetKeyValue(App.Ini_Setting.WindowSettings__SettingsWindow_TopLeft, String.Empty); + if (!String.IsNullOrEmpty(TopLeft)) + { + string[] tl = TopLeft.Split(';'); + Top = double.Parse(tl[0]); + Left = double.Parse(tl[1]); + + // Check Screen ! * To Do * Check Screen Dimensions * + } + } + catch (Exception ex) { App.log.Error("Error Loading Settings Window Settings", ex); } + } + + /// + /// Set the Top N' Left for the Settings Window + /// + /// Top Double + /// Left Double + internal void SetWindowSetting_TopLeft_SettingsWindow(double Top, double Left) + { + if (!Double.IsNaN(Top) && !Double.IsNaN(Left)) + App.inifile.SetKeyValue(App.Ini_Setting.WindowSettings__SettingsWindow_TopLeft, (Top.ToString() + ";" + Left.ToString())); + } + + /// + /// Retrieve the Top N' Left for the Settings Window + /// + /// Top Double + /// Left Double + internal void GetWindowSetting_TopLeft_LogViewerWindow(ref double Top, ref double Left) + { + // Load the Window at the correct position + try + { + string TopLeft = App.inifile.GetKeyValue(App.Ini_Setting.WindowSettings__LogViewerWindow_TopLeft, String.Empty); + if (!String.IsNullOrEmpty(TopLeft)) + { + string[] tl = TopLeft.Split(';'); + Top = double.Parse(tl[0]); + Left = double.Parse(tl[1]); + + // Check Screen ! * To Do * Check Screen Dimensions * + } + } + catch (Exception ex) { App.log.Error("Error Loading Settings Window Settings", ex); } + } + + /// + /// Set the Top N' Left for the Settings Window + /// + /// Top Double + /// Left Double + internal void SetWindowSetting_TopLeft_LogViewerWindow(double Top, double Left) + { + if (!Double.IsNaN(Top) && !Double.IsNaN(Left)) + App.inifile.SetKeyValue(App.Ini_Setting.WindowSettings__LogViewerWindow_TopLeft, (Top.ToString() + ";" + Left.ToString())); + } + + /// + /// Retrieve the Top N' Left for the Settings Window + /// + /// Top Double + /// Left Double + internal void GetWindowSetting_TopLeft_AboutWindow(ref double Top, ref double Left) + { + // Load the Window at the correct position + try + { + string TopLeft = App.inifile.GetKeyValue(App.Ini_Setting.WindowSettings__AboutWindow_TopLeft, String.Empty); + if (!String.IsNullOrEmpty(TopLeft)) + { + string[] tl = TopLeft.Split(';'); + Top = double.Parse(tl[0]); + Left = double.Parse(tl[1]); + + // Check Screen ! * To Do * Check Screen Dimensions * + } + } + catch (Exception ex) { App.log.Error("Error Loading Settings Window Settings", ex); } + } + + /// + /// Set the Top N' Left for the Settings Window + /// + /// Top Double + /// Left Double + internal void SetWindowSetting_TopLeft_AboutWindow(double Top, double Left) + { + if (!Double.IsNaN(Top) && !Double.IsNaN(Left)) + App.inifile.SetKeyValue(App.Ini_Setting.WindowSettings__AboutWindow_TopLeft, (Top.ToString() + ";" + Left.ToString())); + } + + #endregion + + #region Special Circumstances + + /// + /// HL7Messaging puts itself in the Windows\Run to start-up automatically, + /// If we monitor HL7Messaging then we should remove that key * Always * if we are to + /// start with windows. ~This function get's called in those instances where we should + /// check exactly that, and deal with exactly that issue. + /// + internal void DeleteOffendingHL7KeyIfApplicable() + { + bool bStartWithWindowsIsSet = App.inifile.GetKeyValue(App.Ini_Setting.MonitorSettings__Start_With_Windows, true); + if (bStartWithWindowsIsSet) + { + WatchdogConfiguration config = App.xmlfile.ReadData(false); + bool bHasHL7MessagingConfigured = (config.MonitoredProcesses.GetProcessExesByProcessExeFileNameWithoutExtension("HL7Messaging").Length > 0); + if (bHasHL7MessagingConfigured) // Make sure the Key is Deleted + { + if (!RegKey.DeleteKey("Microsoft\\Windows\\CurrentVersion\\Run", "HL7Messenger")) + App.log.Error("Failed to write to Microsoft\\Windows\\CurrentVersion\\Run. Check Permissions."); + } + } + } + + #endregion + } +} diff --git a/@integrate/Snippets.txt b/@integrate/Snippets.txt new file mode 100644 index 0000000..ad14c72 --- /dev/null +++ b/@integrate/Snippets.txt @@ -0,0 +1,24 @@ + private string GetDefaultBrowserPath() + { + try + { + if (ApplicationSettings.appSettings().LogEvents) + { + CrossProductLogging.WriteEventLogFile("Begin GetDefaultBrowserPath"); + } + + string key = @"htmlfile\shell\open\command"; + RegistryKey registryKey = + Registry.ClassesRoot.OpenSubKey(key, false); + // get default browser path + if (ApplicationSettings.appSettings().LogEvents) + { + CrossProductLogging.WriteEventLogFile("End GetDefaultBrowserPath"); + } + return ((string)registryKey.GetValue(null, null)).Split('"')[1]; + } + catch + { + return ""; + } + } \ No newline at end of file diff --git a/@integrate/SysTray.cs b/@integrate/SysTray.cs new file mode 100644 index 0000000..6dfacdd --- /dev/null +++ b/@integrate/SysTray.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; +using System.Windows.Forms; +using WatchdogLib.Thread; +using System.Timers; + +namespace WatchdogLib.WinForms +{ + /// + /// Wrapper Class around .Net NotifyIcon, to make it easier to work with an System Tray Icon. + /// Instantiate the object and set ContextMenu or ContextMenuStrip in order to have a right click menu. + /// Subscripe to the MouseLeftClick, and MouseLeftDoubleClick event to get accurate events to handle, + /// Call Show()/Hide() to show/hide the System Tray Icon, respectively. + /// + public class SysTray + { + #region Public Properties + + public NotifyIcon trayNotify { get; private set; } + public Icon Icon { get { return trayNotify.Icon; } set { trayNotify.Icon = value; } } + public ContextMenu trayMenu { get { return trayNotify.ContextMenu; } set { trayNotify.ContextMenu = value; } } + public ContextMenuStrip trayMenuStrip { get { return trayNotify.ContextMenuStrip; } set { trayNotify.ContextMenuStrip = value; } } + public string toolTip { get { return trayNotify.Text; } set { trayNotify.Text = value; } } + public bool Visible { get { return trayNotify.Visible; } } + + #endregion + + #region Public Events + + public delegate void SingleLeftMouseClick(MouseEventArgs e); + /// + /// Subscribe to get the Left Mouse Single Click Event + /// + public event SingleLeftMouseClick LeftMouseClick; + + public delegate void DoubleLeftMouseClick(MouseEventArgs e); + /// + /// Subscribe to get the Left Mouse Double Click Event + /// + public event DoubleLeftMouseClick LeftMouseDoubleClick; + + public delegate void RightMouseClick(MouseEventArgs e); + /// + /// Subscribe to get any Right Mouse Fired Event, use to redo context menu, if needed + /// + public event RightMouseClick RightMouseFired; + + #endregion + + #region Private Members + + private TTimer SingleClickDetectTimer = null; + private TimeSpan _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks); + private const int _MILISECONDS_FOR_SINGLEMOUSE_CLICKEVENT_TOCOUNT = 500; + private const int _N_SECONDS_TOIGNORE_NEXT_SIGNLEMOUSE_CLICKEVENT = 2; // to avoid tripple clicks, etc... (only sends one double click) + + /// + /// Returns true if enough time since _LastFiredEvent has passed + /// + private bool EnoughTimeSinceLastEventHasElapsed + { + get + { + // 1 second is 10 Million ticks, one Milisecond is 10K + return (DateTime.Now.Ticks - _LastFiredEvent.Ticks) >= (_N_SECONDS_TOIGNORE_NEXT_SIGNLEMOUSE_CLICKEVENT * 1000 * 10000); + } + } + + #endregion + + #region Construction + + /// + /// Construct a System Tray Icon (use public properties like ContextMenu,Icon,toolTip to customize further) + /// + /// pass in an initial ToolTip to display, defaults to "" + /// if null, defaults to systemIcons.Application + //public SysTray(string toolTip = "", Icon icon = null) + public SysTray(string toolTip, Icon icon) + { + // Create internal objects + this.trayNotify = new NotifyIcon(); + this.SingleClickDetectTimer = new TTimer(new ElapsedEventHandler(RealSingleClickDetectTimer_ElapsedEventHandler), _MILISECONDS_FOR_SINGLEMOUSE_CLICKEVENT_TOCOUNT, false, true); + + // Add Single / Double-Click Event Handlers + trayNotify.Click += new EventHandler(trayNotify_Click); + trayNotify.DoubleClick += new EventHandler(trayNotify_DoubleClick); + trayNotify.MouseDown += new MouseEventHandler(trayNotify_MouseDown); + + // Set ToolTip + if (!String.IsNullOrEmpty(toolTip)) + this.toolTip = toolTip; + + // Set Icon + if (icon == null) + this.Icon = new Icon(SystemIcons.Application, 40, 40); + else + this.Icon = icon; + } + + #endregion + + #region Click Event Handlers + + /// + /// Called by NotifyIcon DoubleClick Event, We filter for only the left mouse double-click, + /// event and fire event when neccessary + /// + /// + /// + private void trayNotify_DoubleClick(object sender, EventArgs e) + { + MouseEventArgs args = (MouseEventArgs)e; + if (args.Button == MouseButtons.Left) + { + SingleClickDetectTimer.Stop(); + if (LeftMouseDoubleClick != null && EnoughTimeSinceLastEventHasElapsed) + { + _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks); + LeftMouseDoubleClick(new MouseEventArgs(MouseButtons.Left, 2, 0, 0, 0)); + } + } + } + + /// + // Called by NotifyIcon Click Event, We filter for only the left mouse click, + /// event and fire event when neccessary + /// + /// + /// + private void trayNotify_Click(object sender, EventArgs e) + { + MouseEventArgs args = (MouseEventArgs) e; + if (args.Button == MouseButtons.Left && EnoughTimeSinceLastEventHasElapsed) + SingleClickDetectTimer.Start(); // Start Single Click Detect Timer + } + + /// + /// In order to accurately re-do a context menu, we handle MouseDown for the + /// Right-Mouse click. Mouse Down comes in before the click event, which gives + /// the caller an opportunity to handle/recreate the context menu dynamically, if needed + /// + /// + /// + void trayNotify_MouseDown(object sender, MouseEventArgs e) + { + MouseEventArgs args = (MouseEventArgs)e; + if (args.Button == MouseButtons.Right && (RightMouseFired != null)) + RightMouseFired(args); + } + + /// + /// Used to detect ONLY Single Clicks, since a single-click and then a double-click fires, + /// we want to ignore the first click,and first see if a double-click comes in, if so, ignore + /// the single click, otherwise send it. (this is done by trayNotify_Click & transNotify_DoubleClick) + /// + /// + /// + private void RealSingleClickDetectTimer_ElapsedEventHandler(object sender, ElapsedEventArgs e) + { + SingleClickDetectTimer.Stop(); + if (LeftMouseClick != null) + { + _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks); + LeftMouseClick(new MouseEventArgs(MouseButtons.Left, 1, 0, 0, 0)); + } + } + + #endregion + + #region Show N' Hide + + /// + /// Show the System Tray Icon + /// + public void Show() + { + trayNotify.Visible = true; + } + + /// + /// Hide the System Tray Icon + /// + public void Hide() + { + trayNotify.Visible = false; + } + + #endregion + + #region Public ShowBallon + + /// + /// Pops up a Ballon over the System Tray Icon + /// + /// Specify the Timeout in Seconds + //public void ShowBallon_Default(string BallonTipTitle, string BallonTipText, int nTimeoutInSeconds = 10) + public void ShowBallon_Default(string BallonTipTitle, string BallonTipText, int nTimeoutInSeconds) + { + trayNotify.ShowBalloonTip((nTimeoutInSeconds * 1000), BallonTipTitle, BallonTipText, ToolTipIcon.None); + } + + /// + /// Pops up a Error Ballon over the System Tray Icon + /// + /// Specify the Timeout in Seconds + //public void ShowBallon_Error(string BallonTipTitle, string BallonTipText, int nTimeoutInSeconds = 10) + public void ShowBallon_Error(string BallonTipTitle, string BallonTipText, int nTimeoutInSeconds) + { + trayNotify.ShowBalloonTip((nTimeoutInSeconds * 1000), BallonTipTitle, BallonTipText, ToolTipIcon.Error); + } + + /// + /// Pops up a Warning Ballon over the System Tray Icon + /// + /// Specify the Timeout in Seconds + //public void ShowBallon_Warning(string BallonTipTitle, string BallonTipText, int nTimeoutInSeconds = 10) + public void ShowBallon_Warning(string BallonTipTitle, string BallonTipText, int nTimeoutInSeconds) + { + trayNotify.ShowBalloonTip((nTimeoutInSeconds * 1000), BallonTipTitle, BallonTipText, ToolTipIcon.Warning); + } + + /// + /// Pops up a Info Ballon over the System Tray Icon + /// + /// Specify the Timeout in Seconds + //public void ShowBallon_Info(string BallonTipTitle, string BallonTipText, int nTimeoutInSeconds = 10) + public void ShowBallon_Info(string BallonTipTitle, string BallonTipText, int nTimeoutInSeconds) + { + trayNotify.ShowBalloonTip((nTimeoutInSeconds * 1000), BallonTipTitle, BallonTipText, ToolTipIcon.Info); + } + + #endregion + } +} diff --git a/@integrate/TTimer.cs b/@integrate/TTimer.cs new file mode 100644 index 0000000..17a24d9 --- /dev/null +++ b/@integrate/TTimer.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Timers; +using System.Windows.Threading; +using WinThread = System.Threading; + +namespace WatchdogLib.Thread +{ + /// + /// Wrapper class around Timer Objects + /// + public class TTimer : IDisposable + { + // private Members + private Timer _Timer = new Timer(); + private bool _disposed = false; + private Dispatcher _Dispatcher = null; + private ElapsedEventHandler _DispatchedElapsedEvent = null; + + /// + /// Creates a new Multi-threaded System.Timer + /// + /// Event Handler for Timer + /// Interval in Miliseconds + /// True to start the timer upon creation, false otherwise + /// A Timer Object, which should be Disposed by Caller + //public TTimer(ElapsedEventHandler ElapsedHandler, int IntervalMiliseconds = 1000, bool StartEnabled = false, bool bUseDispatcher = true) + public TTimer(ElapsedEventHandler ElapsedHandler, int IntervalMiliseconds, bool StartEnabled, bool bUseDispatcher) + { + if (ElapsedHandler != null) + { + _Timer = new System.Timers.Timer(); + + // The Primary Dispatcher thread is the thread that called us + if (bUseDispatcher) + { + _Dispatcher = Dispatcher.CurrentDispatcher; + _DispatchedElapsedEvent = ElapsedHandler; + _Timer.Elapsed += new ElapsedEventHandler(_Timer_Elapsed); + } + else + { + _Timer.Elapsed += ElapsedHandler; + } + + // Set the Interval / start + _Timer.Interval = IntervalMiliseconds; + _Timer.Enabled = StartEnabled; + if (StartEnabled) + _Timer.Start(); + + // Keep the timer alive + GC.KeepAlive(_Timer); + } + } + + /// + /// For Dispatching the Event to the Primary Dispatcher Thread + /// + void _Timer_Elapsed(object sender, ElapsedEventArgs e) + { + object[] param_s = new object[] { sender, e }; + _Dispatcher.Invoke((ElapsedEventHandler)_DispatchedElapsedEvent, param_s); + } + + /// + /// Manually Start the Timer + /// + public void Start() + { + Stop(); // First Stop(), an existing Timer + _Timer.Enabled = true; + _Timer.Start(); + } + + /// + /// Manually Start the Timer at a new Interval + /// + /// Interval in Miliseconds + public void Start(uint IntervalMiliseconds) + { + Stop(); // First Stop(), an existing Timer + _Timer.Interval = IntervalMiliseconds; + _Timer.Enabled = true; + _Timer.Start(); + } + + /// + /// Manually Start the Timer at a new Interval + /// + /// Interval as a TimeSpan + public void Start(TimeSpan tsInterval) + { + Stop(); // First Stop(), an existing Timer + _Timer.Interval = tsInterval.TotalMilliseconds; + _Timer.Enabled = true; + _Timer.Start(); + } + + /// + /// Manually Stop the Timer + /// + public void Stop() + { + _Timer.Enabled = false; + _Timer.Stop(); + } + + #region IDisposable Members + + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if (_Timer != null) + _Timer.Dispose(); + } + + // Indicate that the instance has been disposed. + _Timer = null; + _disposed = true; + } + } + + #endregion + } +} diff --git a/@integrate/Trinet.Core.IO.Ntfs/AlternateDataStreamInfo.cs b/@integrate/Trinet.Core.IO.Ntfs/AlternateDataStreamInfo.cs new file mode 100644 index 0000000..ef7f713 --- /dev/null +++ b/@integrate/Trinet.Core.IO.Ntfs/AlternateDataStreamInfo.cs @@ -0,0 +1,654 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Security; +using System.Security.Permissions; + +namespace Trinet.Core.IO.Ntfs +{ + /// + /// Represents the details of an alternative data stream. + /// + [DebuggerDisplay("{FullPath}")] + public sealed class AlternateDataStreamInfo : IEquatable + { + #region Private Data + + private readonly string _fullPath; + private readonly string _filePath; + private readonly string _streamName; + private readonly FileStreamType _streamType; + private readonly FileStreamAttributes _attributes; + private readonly long _size; + private readonly bool _exists; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// + /// The full path of the file. + /// This argument must not be . + /// + /// + /// The containing the stream information. + /// + internal AlternateDataStreamInfo(string filePath, SafeNativeMethods.Win32StreamInfo info) + { + _filePath = filePath; + _streamName = info.StreamName; + _streamType = info.StreamType; + _attributes = info.StreamAttributes; + _size = info.StreamSize; + _exists = true; + + _fullPath = SafeNativeMethods.BuildStreamPath(_filePath, _streamName); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The full path of the file. + /// This argument must not be . + /// + /// + /// The name of the stream + /// This argument must not be . + /// + /// + /// The full path of the stream. + /// If this argument is , it will be generated from the + /// and arguments. + /// + /// + /// if the stream exists; + /// otherwise, . + /// + internal AlternateDataStreamInfo(string filePath, string streamName, string fullPath, bool exists) + { + if (string.IsNullOrEmpty(fullPath)) fullPath = SafeNativeMethods.BuildStreamPath(filePath, streamName); + _streamType = FileStreamType.AlternateDataStream; + + _filePath = filePath; + _streamName = streamName; + _fullPath = fullPath; + _exists = exists; + + if (_exists) + { + _size = SafeNativeMethods.GetFileSize(_fullPath); + } + } + + #endregion + + #region Properties + + /// + /// Returns the full path of this stream. + /// + /// + /// The full path of this stream. + /// + public string FullPath + { + get { return _fullPath; } + } + + /// + /// Returns the full path of the file which contains the stream. + /// + /// + /// The full file-system path of the file which contains the stream. + /// + public string FilePath + { + get { return _filePath; } + } + + /// + /// Returns the name of the stream. + /// + /// + /// The name of the stream. + /// + public string Name + { + get { return _streamName; } + } + + /// + /// Returns a flag indicating whether the specified stream exists. + /// + /// + /// if the stream exists; + /// otherwise, . + /// + public bool Exists + { + get { return _exists; } + } + + /// + /// Returns the size of the stream, in bytes. + /// + /// + /// The size of the stream, in bytes. + /// + public long Size + { + get { return _size; } + } + + /// + /// Returns the type of data. + /// + /// + /// One of the values. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public FileStreamType StreamType + { + get { return _streamType; } + } + + /// + /// Returns attributes of the data stream. + /// + /// + /// A combination of values. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public FileStreamAttributes Attributes + { + get { return _attributes; } + } + + #endregion + + #region Methods + + #region -IEquatable + + /// + /// Returns a that represents the current instance. + /// + /// + /// A that represents the current instance. + /// + public override string ToString() + { + return this.FullPath; + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// A hash code for the current . + /// + public override int GetHashCode() + { + var comparer = StringComparer.OrdinalIgnoreCase; + return comparer.GetHashCode(_filePath ?? string.Empty) + ^ comparer.GetHashCode(_streamName ?? string.Empty); + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// An object to compare with this object. + /// + /// + /// if the current object is equal to the parameter; + /// otherwise, . + /// + public override bool Equals(object obj) + { + if (object.ReferenceEquals(null, obj)) return false; + if (object.ReferenceEquals(this, obj)) return true; + + AlternateDataStreamInfo other = obj as AlternateDataStreamInfo; + if (!object.ReferenceEquals(null, other)) return this.Equals(other); + + return false; + } + + /// + /// Returns a value indicating whether + /// this instance is equal to another instance. + /// + /// + /// The instance to compare to. + /// + /// + /// if the current object is equal to the parameter; + /// otherwise, . + /// + public bool Equals(AlternateDataStreamInfo other) + { + if (object.ReferenceEquals(null, other)) return false; + if (object.ReferenceEquals(this, other)) return true; + + var comparer = StringComparer.OrdinalIgnoreCase; + return comparer.Equals(this._filePath ?? string.Empty, other._filePath ?? string.Empty) + && comparer.Equals(this._streamName ?? string.Empty, other._streamName ?? string.Empty); + } + + /// + /// The equality operator. + /// + /// + /// The first object. + /// + /// + /// The second object. + /// + /// + /// if the two objects are equal; + /// otherwise, . + /// + public static bool operator ==(AlternateDataStreamInfo first, AlternateDataStreamInfo second) + { + if (object.ReferenceEquals(first, second)) return true; + if (object.ReferenceEquals(null, first)) return false; + if (object.ReferenceEquals(null, second)) return false; + return first.Equals(second); + } + + /// + /// The inequality operator. + /// + /// + /// The first object. + /// + /// + /// The second object. + /// + /// + /// if the two objects are not equal; + /// otherwise, . + /// + public static bool operator !=(AlternateDataStreamInfo first, AlternateDataStreamInfo second) + { + if (object.ReferenceEquals(first, second)) return false; + if (object.ReferenceEquals(null, first)) return true; + if (object.ReferenceEquals(null, second)) return true; + return !first.Equals(second); + } + + #endregion + + #region -Delete + + /// + /// Deletes this stream from the parent file. + /// + /// + /// if the stream was deleted; + /// otherwise, . + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + /// + /// The specified file is in use. + /// + /// + /// The path of the stream is invalid. + /// + public bool Delete() + { + const FileIOPermissionAccess permAccess = FileIOPermissionAccess.Write; + new FileIOPermission(permAccess, _filePath).Demand(); + return SafeNativeMethods.SafeDeleteFile(this.FullPath); + } + + #endregion + + #region -Open + + /// + /// Calculates the access to demand. + /// + /// + /// The . + /// + /// + /// The . + /// + /// + /// The . + /// + private static FileIOPermissionAccess CalculateAccess(FileMode mode, FileAccess access) + { + FileIOPermissionAccess permAccess = FileIOPermissionAccess.NoAccess; + switch (mode) + { + case FileMode.Append: + permAccess = FileIOPermissionAccess.Append; + break; + + case FileMode.Create: + case FileMode.CreateNew: + case FileMode.OpenOrCreate: + case FileMode.Truncate: + permAccess = FileIOPermissionAccess.Write; + break; + + case FileMode.Open: + permAccess = FileIOPermissionAccess.Read; + break; + } + switch (access) + { + case FileAccess.ReadWrite: + permAccess |= FileIOPermissionAccess.Write; + permAccess |= FileIOPermissionAccess.Read; + break; + + case FileAccess.Write: + permAccess |= FileIOPermissionAccess.Write; + break; + + case FileAccess.Read: + permAccess |= FileIOPermissionAccess.Read; + break; + } + + return permAccess; + } + + /// + /// Opens this alternate data stream. + /// + /// + /// A value that specifies whether a stream is created if one does not exist, + /// and determines whether the contents of existing streams are retained or overwritten. + /// + /// + /// A value that specifies the operations that can be performed on the stream. + /// + /// + /// A value specifying the type of access other threads have to the file. + /// + /// + /// The size of the buffer to use. + /// + /// + /// to enable async-IO; + /// otherwise, . + /// + /// + /// A for this alternate data stream. + /// + /// + /// is less than or equal to zero. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + /// + /// The specified file is in use. + /// + /// + /// The path of the stream is invalid. + /// + /// + /// There was an error opening the stream. + /// + public FileStream Open(FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) + { + if (0 >= bufferSize) throw new ArgumentOutOfRangeException("bufferSize", bufferSize, null); + + FileIOPermissionAccess permAccess = CalculateAccess(mode, access); + new FileIOPermission(permAccess, _filePath).Demand(); + + SafeNativeMethods.NativeFileFlags flags = useAsync ? SafeNativeMethods.NativeFileFlags.Overlapped : 0; + var handle = SafeNativeMethods.SafeCreateFile(this.FullPath, access.ToNative(), share, IntPtr.Zero, mode, flags, IntPtr.Zero); + if (handle.IsInvalid) SafeNativeMethods.ThrowLastIOError(this.FullPath); + return new FileStream(handle, access, bufferSize, useAsync); + } + + /// + /// Opens this alternate data stream. + /// + /// + /// A value that specifies whether a stream is created if one does not exist, + /// and determines whether the contents of existing streams are retained or overwritten. + /// + /// + /// A value that specifies the operations that can be performed on the stream. + /// + /// + /// A value specifying the type of access other threads have to the file. + /// + /// + /// The size of the buffer to use. + /// + /// + /// A for this alternate data stream. + /// + /// + /// is less than or equal to zero. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + /// + /// The specified file is in use. + /// + /// + /// The path of the stream is invalid. + /// + /// + /// There was an error opening the stream. + /// + public FileStream Open(FileMode mode, FileAccess access, FileShare share, int bufferSize) + { + return this.Open(mode, access, share, bufferSize, false); + } + + /// + /// Opens this alternate data stream. + /// + /// + /// A value that specifies whether a stream is created if one does not exist, + /// and determines whether the contents of existing streams are retained or overwritten. + /// + /// + /// A value that specifies the operations that can be performed on the stream. + /// + /// + /// A value specifying the type of access other threads have to the file. + /// + /// + /// A for this alternate data stream. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + /// + /// The specified file is in use. + /// + /// + /// The path of the stream is invalid. + /// + /// + /// There was an error opening the stream. + /// + public FileStream Open(FileMode mode, FileAccess access, FileShare share) + { + return this.Open(mode, access, share, SafeNativeMethods.DefaultBufferSize, false); + } + + /// + /// Opens this alternate data stream. + /// + /// + /// A value that specifies whether a stream is created if one does not exist, + /// and determines whether the contents of existing streams are retained or overwritten. + /// + /// + /// A value that specifies the operations that can be performed on the stream. + /// + /// + /// A for this alternate data stream. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + /// + /// The specified file is in use. + /// + /// + /// The path of the stream is invalid. + /// + /// + /// There was an error opening the stream. + /// + public FileStream Open(FileMode mode, FileAccess access) + { + return this.Open(mode, access, FileShare.None, SafeNativeMethods.DefaultBufferSize, false); + } + + /// + /// Opens this alternate data stream. + /// + /// + /// A value that specifies whether a stream is created if one does not exist, + /// and determines whether the contents of existing streams are retained or overwritten. + /// + /// + /// A for this alternate data stream. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + /// + /// The specified file is in use. + /// + /// + /// The path of the stream is invalid. + /// + /// + /// There was an error opening the stream. + /// + public FileStream Open(FileMode mode) + { + FileAccess access = (FileMode.Append == mode) ? FileAccess.Write : FileAccess.ReadWrite; + return this.Open(mode, access, FileShare.None, SafeNativeMethods.DefaultBufferSize, false); + } + + #endregion + + #region -OpenRead / OpenWrite / OpenText + + /// + /// Opens this stream for reading. + /// + /// + /// A read-only for this stream. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + /// + /// The specified file is in use. + /// + /// + /// The path of the stream is invalid. + /// + /// + /// There was an error opening the stream. + /// + public FileStream OpenRead() + { + return this.Open(FileMode.Open, FileAccess.Read, FileShare.Read); + } + + /// + /// Opens this stream for writing. + /// + /// + /// A write-only for this stream. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + /// + /// The specified file is in use. + /// + /// + /// The path of the stream is invalid. + /// + /// + /// There was an error opening the stream. + /// + public FileStream OpenWrite() + { + return this.Open(FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); + } + + /// + /// Opens this stream as a text file. + /// + /// + /// A which can be used to read the contents of this stream. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + /// + /// The specified file is in use. + /// + /// + /// The path of the stream is invalid. + /// + /// + /// There was an error opening the stream. + /// + public StreamReader OpenText() + { + Stream fileStream = this.Open(FileMode.Open, FileAccess.Read, FileShare.Read); + return new StreamReader(fileStream); + } + + #endregion + + #endregion + } +} diff --git a/@integrate/Trinet.Core.IO.Ntfs/FileStreamAttributes.cs b/@integrate/Trinet.Core.IO.Ntfs/FileStreamAttributes.cs new file mode 100644 index 0000000..dde135a --- /dev/null +++ b/@integrate/Trinet.Core.IO.Ntfs/FileStreamAttributes.cs @@ -0,0 +1,32 @@ +using System; + +namespace Trinet.Core.IO.Ntfs +{ + /// + /// Represents the attributes of a file stream. + /// + [Flags] + public enum FileStreamAttributes + { + /// + /// No attributes. + /// + None = 0, + /// + /// Set if the stream contains data that is modified when read. + /// + ModifiedWhenRead = 1, + /// + /// Set if the stream contains security data. + /// + ContainsSecurity = 2, + /// + /// Set if the stream contains properties. + /// + ContainsProperties = 4, + /// + /// Set if the stream is sparse. + /// + Sparse = 8, + } +} diff --git a/@integrate/Trinet.Core.IO.Ntfs/FileStreamType.cs b/@integrate/Trinet.Core.IO.Ntfs/FileStreamType.cs new file mode 100644 index 0000000..a0201a4 --- /dev/null +++ b/@integrate/Trinet.Core.IO.Ntfs/FileStreamType.cs @@ -0,0 +1,56 @@ +using System; + +namespace Trinet.Core.IO.Ntfs +{ + /// + /// Represents the type of data in a stream. + /// + public enum FileStreamType + { + /// + /// Unknown stream type. + /// + Unknown = 0, + /// + /// Standard data. + /// + Data = 1, + /// + /// Extended attribute data. + /// + ExtendedAttributes = 2, + /// + /// Security data. + /// + SecurityData = 3, + /// + /// Alternate data stream. + /// + AlternateDataStream = 4, + /// + /// Hard link information. + /// + Link = 5, + /// + /// Property data. + /// + PropertyData = 6, + /// + /// Object identifiers. + /// + ObjectId = 7, + /// + /// Reparse points. + /// + ReparseData = 8, + /// + /// Sparse file. + /// + SparseBlock = 9, + /// + /// Transactional data. + /// (Undocumented - BACKUP_TXFS_DATA) + /// + TransactionData = 10, + } +} diff --git a/@integrate/Trinet.Core.IO.Ntfs/FileSystem.cs b/@integrate/Trinet.Core.IO.Ntfs/FileSystem.cs new file mode 100644 index 0000000..c20a92c --- /dev/null +++ b/@integrate/Trinet.Core.IO.Ntfs/FileSystem.cs @@ -0,0 +1,452 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security; +using System.Security.Permissions; + +namespace Trinet.Core.IO.Ntfs +{ + using Resources = Properties.Resources; + + /// + /// File-system utilities. + /// + public static class FileSystem + { + #region Create FileSystemInfo + + /// + /// Creates a for the specified path. + /// + /// + /// The path of the file or directory. + /// + /// + /// The representing the file or directory. + /// + /// + /// is or empty. + /// + private static FileSystemInfo CreateInfo(string path) + { + if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path"); + + path = Path.GetFullPath(path); + if (!File.Exists(path) && Directory.Exists(path)) return new DirectoryInfo(path); + return new FileInfo(path); + } + + #endregion + + #region List Streams + + /// + /// (Extension Method)
+ /// Returns a read-only list of alternate data streams for the specified file. + ///
+ /// + /// The to inspect. + /// + /// + /// A read-only list of objects + /// representing the alternate data streams for the specified file, if any. + /// If no streams are found, returns an empty list. + /// + /// + /// is . + /// + /// + /// The specified does not exist. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission. + /// + public static IList ListAlternateDataStreams(this FileSystemInfo file) + { + if (null == file) throw new ArgumentNullException("file"); + if (!file.Exists) throw new FileNotFoundException(null, file.FullName); + + string path = file.FullName; + new FileIOPermission(FileIOPermissionAccess.Read, path).Demand(); + + return SafeNativeMethods.ListStreams(path) + .Select(s => new AlternateDataStreamInfo(path, s)) + .ToList().AsReadOnly(); + } + + /// + /// Returns a read-only list of alternate data streams for the specified file. + /// + /// + /// The full path of the file to inspect. + /// + /// + /// A read-only list of objects + /// representing the alternate data streams for the specified file, if any. + /// + /// + /// is or an empty string. + /// + /// + /// is not a valid file path. + /// + /// + /// The specified does not exist. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission. + /// + public static IList ListAlternateDataStreams(string filePath) + { + if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("filePath"); + return CreateInfo(filePath).ListAlternateDataStreams(); + } + + #endregion + + #region Stream Exists + + /// + /// (Extension Method)
+ /// Returns a flag indicating whether the specified alternate data stream exists. + ///
+ /// + /// The to inspect. + /// + /// + /// The name of the stream to find. + /// + /// + /// if the specified stream exists; + /// otherwise, . + /// + /// + /// is . + /// + /// + /// contains invalid characters. + /// + public static bool AlternateDataStreamExists(this FileSystemInfo file, string streamName) + { + if (null == file) throw new ArgumentNullException("file"); + SafeNativeMethods.ValidateStreamName(streamName); + + string path = SafeNativeMethods.BuildStreamPath(file.FullName, streamName); + return -1 != SafeNativeMethods.SafeGetFileAttributes(path); + } + + /// + /// Returns a flag indicating whether the specified alternate data stream exists. + /// + /// + /// The path of the file to inspect. + /// + /// + /// The name of the stream to find. + /// + /// + /// if the specified stream exists; + /// otherwise, . + /// + /// + /// is or an empty string. + /// + /// + /// is not a valid file path. + /// -or- + /// contains invalid characters. + /// + public static bool AlternateDataStreamExists(string filePath, string streamName) + { + if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("filePath"); + return CreateInfo(filePath).AlternateDataStreamExists(streamName); + } + + #endregion + + #region Open Stream + + /// + /// (Extension Method)
+ /// Opens an alternate data stream. + ///
+ /// + /// The which contains the stream. + /// + /// + /// The name of the stream to open. + /// + /// + /// One of the values, indicating how the stream is to be opened. + /// + /// + /// An representing the stream. + /// + /// + /// is . + /// + /// + /// The specified was not found. + /// + /// + /// contains invalid characters. + /// + /// + /// is either or . + /// + /// + /// is , and the stream doesn't exist. + /// -or- + /// is , and the stream already exists. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + public static AlternateDataStreamInfo GetAlternateDataStream(this FileSystemInfo file, string streamName, FileMode mode) + { + if (null == file) throw new ArgumentNullException("file"); + if (!file.Exists) throw new FileNotFoundException(null, file.FullName); + SafeNativeMethods.ValidateStreamName(streamName); + + if (FileMode.Truncate == mode || FileMode.Append == mode) + { + throw new NotSupportedException(string.Format(Resources.Culture, + Resources.Error_InvalidMode, mode)); + } + + FileIOPermissionAccess permAccess = (FileMode.Open == mode) ? FileIOPermissionAccess.Read : FileIOPermissionAccess.Read | FileIOPermissionAccess.Write; + new FileIOPermission(permAccess, file.FullName).Demand(); + + string path = SafeNativeMethods.BuildStreamPath(file.FullName, streamName); + bool exists = -1 != SafeNativeMethods.SafeGetFileAttributes(path); + + if (!exists && FileMode.Open == mode) + { + throw new IOException(string.Format(Resources.Culture, + Resources.Error_StreamNotFound, streamName, file.Name)); + } + if (exists && FileMode.CreateNew == mode) + { + throw new IOException(string.Format(Resources.Culture, + Resources.Error_StreamExists, streamName, file.Name)); + } + + return new AlternateDataStreamInfo(file.FullName, streamName, path, exists); + } + + /// + /// (Extension Method)
+ /// Opens an alternate data stream. + ///
+ /// + /// The which contains the stream. + /// + /// + /// The name of the stream to open. + /// + /// + /// An representing the stream. + /// + /// + /// is . + /// + /// + /// The specified was not found. + /// + /// + /// contains invalid characters. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + public static AlternateDataStreamInfo GetAlternateDataStream(this FileSystemInfo file, string streamName) + { + return file.GetAlternateDataStream(streamName, FileMode.OpenOrCreate); + } + + /// + /// Opens an alternate data stream. + /// + /// + /// The path of the file which contains the stream. + /// + /// + /// The name of the stream to open. + /// + /// + /// One of the values, indicating how the stream is to be opened. + /// + /// + /// An representing the stream. + /// + /// + /// is or an empty string. + /// + /// + /// The specified was not found. + /// + /// + /// is not a valid file path. + /// -or- + /// contains invalid characters. + /// + /// + /// is either or . + /// + /// + /// is , and the stream doesn't exist. + /// -or- + /// is , and the stream already exists. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + public static AlternateDataStreamInfo GetAlternateDataStream(string filePath, string streamName, FileMode mode) + { + if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("filePath"); + return CreateInfo(filePath).GetAlternateDataStream(streamName, mode); + } + + /// + /// Opens an alternate data stream. + /// + /// + /// The path of the file which contains the stream. + /// + /// + /// The name of the stream to open. + /// + /// + /// An representing the stream. + /// + /// + /// is or an empty string. + /// + /// + /// The specified was not found. + /// + /// + /// is not a valid file path. + /// -or- + /// contains invalid characters. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + public static AlternateDataStreamInfo GetAlternateDataStream(string filePath, string streamName) + { + return GetAlternateDataStream(filePath, streamName, FileMode.OpenOrCreate); + } + + #endregion + + #region Delete Stream + + /// + /// (Extension Method)
+ /// Deletes the specified alternate data stream if it exists. + ///
+ /// + /// The to inspect. + /// + /// + /// The name of the stream to delete. + /// + /// + /// if the specified stream is deleted; + /// otherwise, . + /// + /// + /// is . + /// + /// + /// contains invalid characters. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + /// + /// The specified file is in use. + /// + public static bool DeleteAlternateDataStream(this FileSystemInfo file, string streamName) + { + if (null == file) throw new ArgumentNullException("file"); + SafeNativeMethods.ValidateStreamName(streamName); + + const FileIOPermissionAccess permAccess = FileIOPermissionAccess.Write; + new FileIOPermission(permAccess, file.FullName).Demand(); + + var result = false; + if (file.Exists) + { + string path = SafeNativeMethods.BuildStreamPath(file.FullName, streamName); + if (-1 != SafeNativeMethods.SafeGetFileAttributes(path)) + { + result = SafeNativeMethods.SafeDeleteFile(path); + } + } + + return result; + } + + /// + /// Deletes the specified alternate data stream if it exists. + /// + /// + /// The path of the file to inspect. + /// + /// + /// The name of the stream to find. + /// + /// + /// if the specified stream is deleted; + /// otherwise, . + /// + /// + /// is or an empty string. + /// + /// + /// is not a valid file path. + /// -or- + /// contains invalid characters. + /// + /// + /// The caller does not have the required permission. + /// + /// + /// The caller does not have the required permission, or the file is read-only. + /// + /// + /// The specified file is in use. + /// + public static bool DeleteAlternateDataStream(string filePath, string streamName) + { + if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("filePath"); + return CreateInfo(filePath).DeleteAlternateDataStream(streamName); + } + + #endregion + } +} diff --git a/@integrate/Trinet.Core.IO.Ntfs/SafeHGlobalHandle.cs b/@integrate/Trinet.Core.IO.Ntfs/SafeHGlobalHandle.cs new file mode 100644 index 0000000..71e19db --- /dev/null +++ b/@integrate/Trinet.Core.IO.Ntfs/SafeHGlobalHandle.cs @@ -0,0 +1,118 @@ +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; + +namespace Trinet.Core.IO.Ntfs +{ + /// + /// A for a global memory allocation. + /// + internal sealed class SafeHGlobalHandle : SafeHandle + { + #region Private Data + + private readonly int _size; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// + /// The initial handle value. + /// + /// + /// The size of this memory block, in bytes. + /// + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private SafeHGlobalHandle(IntPtr toManage, int size) : base(IntPtr.Zero, true) + { + _size = size; + base.SetHandle(toManage); + } + + /// + /// Initializes a new instance of the class. + /// + private SafeHGlobalHandle() : base(IntPtr.Zero, true) + { + } + + #endregion + + #region Properties + + /// + /// Gets a value indicating whether the handle value is invalid. + /// + /// + /// if the handle value is invalid; + /// otherwise, . + /// + public override bool IsInvalid + { + get { return IntPtr.Zero == base.handle; } + } + + /// + /// Returns the size of this memory block. + /// + /// + /// The size of this memory block, in bytes. + /// + public int Size + { + get { return _size; } + } + + #endregion + + #region Methods + + /// + /// Allocates memory from the unmanaged memory of the process using GlobalAlloc. + /// + /// + /// The number of bytes in memory required. + /// + /// + /// A representing the memory. + /// + /// + /// There is insufficient memory to satisfy the request. + /// + public static SafeHGlobalHandle Allocate(int bytes) + { + return new SafeHGlobalHandle(Marshal.AllocHGlobal(bytes), bytes); + } + + /// + /// Returns an invalid handle. + /// + /// + /// An invalid . + /// + public static SafeHGlobalHandle Invalid() + { + return new SafeHGlobalHandle(); + } + + /// + /// Executes the code required to free the handle. + /// + /// + /// if the handle is released successfully; + /// otherwise, in the event of a catastrophic failure, . + /// In this case, it generates a releaseHandleFailed MDA Managed Debugging Assistant. + /// + protected override bool ReleaseHandle() + { + Marshal.FreeHGlobal(base.handle); + return true; + } + + #endregion + } +} diff --git a/@integrate/Trinet.Core.IO.Ntfs/SafeNativeMethods.cs b/@integrate/Trinet.Core.IO.Ntfs/SafeNativeMethods.cs new file mode 100644 index 0000000..33028a4 --- /dev/null +++ b/@integrate/Trinet.Core.IO.Ntfs/SafeNativeMethods.cs @@ -0,0 +1,478 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +namespace Trinet.Core.IO.Ntfs +{ + using Resources = Properties.Resources; + + /// + /// Safe native methods. + /// + internal static class SafeNativeMethods + { + #region Constants and flags + + public const int MaxPath = 256; + private const string LongPathPrefix = @"\\?\"; + public const char StreamSeparator = ':'; + public const int DefaultBufferSize = 0x1000; + + private const int ErrorFileNotFound = 2; + + // "Characters whose integer representations are in the range from 1 through 31, + // except for alternate streams where these characters are allowed" + // http://msdn.microsoft.com/en-us/library/aa365247(v=VS.85).aspx + private static readonly char[] InvalidStreamNameChars = Path.GetInvalidFileNameChars().Where(c => c < 1 || c > 31).ToArray(); + + [Flags] + public enum NativeFileFlags : uint + { + WriteThrough = 0x80000000, + Overlapped = 0x40000000, + NoBuffering = 0x20000000, + RandomAccess = 0x10000000, + SequentialScan = 0x8000000, + DeleteOnClose = 0x4000000, + BackupSemantics = 0x2000000, + PosixSemantics = 0x1000000, + OpenReparsePoint = 0x200000, + OpenNoRecall = 0x100000 + } + + [Flags] + public enum NativeFileAccess : uint + { + GenericRead = 0x80000000, + GenericWrite = 0x40000000 + } + + #endregion + + #region P/Invoke Structures + + [StructLayout(LayoutKind.Sequential)] + private struct LargeInteger + { + public readonly int Low; + public readonly int High; + + public long ToInt64() + { + return (this.High * 0x100000000) + this.Low; + } + + /* + public static LargeInteger FromInt64(long value) + { + return new LargeInteger + { + Low = (int)(value & 0x11111111), + High = (int)((value / 0x100000000) & 0x11111111) + }; + } + */ + } + + [StructLayout(LayoutKind.Sequential)] + private struct Win32StreamId + { + public readonly int StreamId; + public readonly int StreamAttributes; + public LargeInteger Size; + public readonly int StreamNameSize; + } + +/* + [StructLayout(LayoutKind.Sequential)] + private struct FileInformationByHandle + { + public int dwFileAttributes; + public LargeInteger ftCreationTime; + public LargeInteger ftLastAccessTime; + public LargeInteger ftLastWriteTime; + public int dwVolumeSerialNumber; + public LargeInteger FileSize; + public int nNumberOfLinks; + public LargeInteger FileIndex; + } +*/ + + #endregion + + #region P/Invoke Methods + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, BestFitMapping = false, ThrowOnUnmappableChar = true)] + private static extern int FormatMessage( + int dwFlags, + IntPtr lpSource, + int dwMessageId, + int dwLanguageId, + StringBuilder lpBuffer, + int nSize, + IntPtr vaListArguments); + + [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern int GetFileAttributes(string fileName); + + [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GetFileSizeEx(SafeFileHandle handle, out LargeInteger size); + + [DllImport("kernel32.dll")] + private static extern int GetFileType(SafeFileHandle handle); + + [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern SafeFileHandle CreateFile( + string name, + NativeFileAccess access, + FileShare share, + IntPtr security, + FileMode mode, + NativeFileFlags flags, + IntPtr template); + + [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DeleteFile(string name); + + [DllImport("kernel32", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool BackupRead( + SafeFileHandle hFile, + ref Win32StreamId pBuffer, + int numberOfBytesToRead, + out int numberOfBytesRead, + [MarshalAs(UnmanagedType.Bool)] bool abort, + [MarshalAs(UnmanagedType.Bool)] bool processSecurity, + ref IntPtr context); + + [DllImport("kernel32", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool BackupRead( + SafeFileHandle hFile, + SafeHGlobalHandle pBuffer, + int numberOfBytesToRead, + out int numberOfBytesRead, + [MarshalAs(UnmanagedType.Bool)] bool abort, + [MarshalAs(UnmanagedType.Bool)] bool processSecurity, + ref IntPtr context); + + [DllImport("kernel32", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool BackupSeek( + SafeFileHandle hFile, + int bytesToSeekLow, + int bytesToSeekHigh, + out int bytesSeekedLow, + out int bytesSeekedHigh, + ref IntPtr context); + + #endregion + + #region Utility Structures + + public struct Win32StreamInfo + { + public FileStreamType StreamType; + public FileStreamAttributes StreamAttributes; + public long StreamSize; + public string StreamName; + } + + #endregion + + #region Utility Methods + + private static int MakeHRFromErrorCode(int errorCode) + { + return (-2147024896 | errorCode); + } + + private static string GetErrorMessage(int errorCode) + { + var lpBuffer = new StringBuilder(0x200); + if (0 != FormatMessage(0x3200, IntPtr.Zero, errorCode, 0, lpBuffer, lpBuffer.Capacity, IntPtr.Zero)) + { + return lpBuffer.ToString(); + } + + return string.Format(Resources.Culture, Resources.Error_UnknownError, errorCode); + } + + private static void ThrowIOError(int errorCode, string path) + { + switch (errorCode) + { + case 0: + { + break; + } + case 2: // File not found + { + if (string.IsNullOrEmpty(path)) throw new FileNotFoundException(); + throw new FileNotFoundException(null, path); + } + case 3: // Directory not found + { + if (string.IsNullOrEmpty(path)) throw new DirectoryNotFoundException(); + throw new DirectoryNotFoundException(string.Format(Resources.Culture, Resources.Error_DirectoryNotFound, path)); + } + case 5: // Access denied + { + if (string.IsNullOrEmpty(path)) throw new UnauthorizedAccessException(); + throw new UnauthorizedAccessException(string.Format(Resources.Culture, Resources.Error_AccessDenied_Path, path)); + } + case 15: // Drive not found + { + if (string.IsNullOrEmpty(path)) throw new DriveNotFoundException(); + throw new DriveNotFoundException(string.Format(Resources.Culture, Resources.Error_DriveNotFound, path)); + } + case 32: // Sharing violation + { + if (string.IsNullOrEmpty(path)) throw new IOException(GetErrorMessage(errorCode), MakeHRFromErrorCode(errorCode)); + throw new IOException(string.Format(Resources.Culture, Resources.Error_SharingViolation, path), MakeHRFromErrorCode(errorCode)); + } + case 80: // File already exists + { + if (!string.IsNullOrEmpty(path)) + { + throw new IOException(string.Format(Resources.Culture, Resources.Error_FileAlreadyExists, path), MakeHRFromErrorCode(errorCode)); + } + break; + } + case 87: // Invalid parameter + { + throw new IOException(GetErrorMessage(errorCode), MakeHRFromErrorCode(errorCode)); + } + case 183: // File or directory already exists + { + if (!string.IsNullOrEmpty(path)) + { + throw new IOException(string.Format(Resources.Culture, Resources.Error_AlreadyExists, path), MakeHRFromErrorCode(errorCode)); + } + break; + } + case 206: // Path too long + { + throw new PathTooLongException(); + } + case 995: // Operation cancelled + { + throw new OperationCanceledException(); + } + default: + { + Marshal.ThrowExceptionForHR(MakeHRFromErrorCode(errorCode)); + break; + } + } + } + + public static void ThrowLastIOError(string path) + { + int errorCode = Marshal.GetLastWin32Error(); + if (0 != errorCode) + { + int hr = Marshal.GetHRForLastWin32Error(); + if (0 <= hr) throw new Win32Exception(errorCode); + ThrowIOError(errorCode, path); + } + } + + public static NativeFileAccess ToNative(this FileAccess access) + { + NativeFileAccess result = 0; + if (FileAccess.Read == (FileAccess.Read & access)) result |= NativeFileAccess.GenericRead; + if (FileAccess.Write == (FileAccess.Write & access)) result |= NativeFileAccess.GenericWrite; + return result; + } + + public static string BuildStreamPath(string filePath, string streamName) + { + string result = filePath; + if (!string.IsNullOrEmpty(filePath)) + { + if (1 == result.Length) result = ".\\" + result; + result += StreamSeparator + streamName + StreamSeparator + "$DATA"; + if (MaxPath <= result.Length) result = LongPathPrefix + result; + } + return result; + } + + public static void ValidateStreamName(string streamName) + { + if (!string.IsNullOrEmpty(streamName) && -1 != streamName.IndexOfAny(InvalidStreamNameChars)) + { + throw new ArgumentException(Resources.Error_InvalidFileChars); + } + } + + public static int SafeGetFileAttributes(string name) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); + + int result = GetFileAttributes(name); + if (-1 == result) + { + int errorCode = Marshal.GetLastWin32Error(); + if (ErrorFileNotFound != errorCode) ThrowLastIOError(name); + } + + return result; + } + + public static bool SafeDeleteFile(string name) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); + + bool result = DeleteFile(name); + if (!result) + { + int errorCode = Marshal.GetLastWin32Error(); + if (ErrorFileNotFound != errorCode) ThrowLastIOError(name); + } + + return result; + } + + public static SafeFileHandle SafeCreateFile(string path, NativeFileAccess access, FileShare share, IntPtr security, FileMode mode, NativeFileFlags flags, IntPtr template) + { + SafeFileHandle result = CreateFile(path, access, share, security, mode, flags, template); + if (!result.IsInvalid && 1 != GetFileType(result)) + { + result.Dispose(); + throw new NotSupportedException(string.Format(Resources.Culture, + Resources.Error_NonFile, path)); + } + + return result; + } + + private static long GetFileSize(string path, SafeFileHandle handle) + { + long result = 0L; + if (null != handle && !handle.IsInvalid) + { + LargeInteger value; + if (GetFileSizeEx(handle, out value)) + { + result = value.ToInt64(); + } + else + { + ThrowLastIOError(path); + } + } + + return result; + } + + public static long GetFileSize(string path) + { + long result = 0L; + if (!string.IsNullOrEmpty(path)) + { + using (SafeFileHandle handle = SafeCreateFile(path, NativeFileAccess.GenericRead, FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero)) + { + result = GetFileSize(path, handle); + } + } + + return result; + } + + public static IList ListStreams(string filePath) + { + if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("filePath"); + if (-1 != filePath.IndexOfAny(Path.GetInvalidPathChars())) throw new ArgumentException(Resources.Error_InvalidFileChars, "filePath"); + + var result = new List(); + + using (SafeFileHandle hFile = SafeCreateFile(filePath, NativeFileAccess.GenericRead, FileShare.Read, IntPtr.Zero, FileMode.Open, NativeFileFlags.BackupSemantics, IntPtr.Zero)) + using (var hName = new StreamName()) + { + if (!hFile.IsInvalid) + { + var streamId = new Win32StreamId(); + int dwStreamHeaderSize = Marshal.SizeOf(streamId); + bool finished = false; + IntPtr context = IntPtr.Zero; + int bytesRead; + string name; + + try + { + while (!finished) + { + // Read the next stream header: + if (!BackupRead(hFile, ref streamId, dwStreamHeaderSize, out bytesRead, false, false, ref context)) + { + finished = true; + } + else if (dwStreamHeaderSize != bytesRead) + { + finished = true; + } + else + { + // Read the stream name: + if (0 >= streamId.StreamNameSize) + { + name = null; + } + else + { + hName.EnsureCapacity(streamId.StreamNameSize); + if (!BackupRead(hFile, hName.MemoryBlock, streamId.StreamNameSize, out bytesRead, false, false, ref context)) + { + name = null; + finished = true; + } + else + { + // Unicode chars are 2 bytes: + name = hName.ReadStreamName(bytesRead >> 1); + } + } + + // Add the stream info to the result: + if (!string.IsNullOrEmpty(name)) + { + result.Add(new Win32StreamInfo + { + StreamType = (FileStreamType)streamId.StreamId, + StreamAttributes = (FileStreamAttributes)streamId.StreamAttributes, + StreamSize = streamId.Size.ToInt64(), + StreamName = name + }); + } + + // Skip the contents of the stream: + int bytesSeekedLow, bytesSeekedHigh; + if (!finished && !BackupSeek(hFile, streamId.Size.Low, streamId.Size.High, out bytesSeekedLow, out bytesSeekedHigh, ref context)) + { + finished = true; + } + } + } + } + finally + { + // Abort the backup: + BackupRead(hFile, hName.MemoryBlock, 0, out bytesRead, true, false, ref context); + } + } + } + + return result; + } + + #endregion + } +} diff --git a/@integrate/Trinet.Core.IO.Ntfs/StreamName.cs b/@integrate/Trinet.Core.IO.Ntfs/StreamName.cs new file mode 100644 index 0000000..fdbe7ff --- /dev/null +++ b/@integrate/Trinet.Core.IO.Ntfs/StreamName.cs @@ -0,0 +1,134 @@ +using System; +using System.Runtime.InteropServices; + +namespace Trinet.Core.IO.Ntfs +{ + internal sealed class StreamName : IDisposable + { + #region Private Data + + private static readonly SafeHGlobalHandle _invalidBlock = SafeHGlobalHandle.Invalid(); + private SafeHGlobalHandle _memoryBlock = _invalidBlock; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + public StreamName() + { + } + + #endregion + + #region Properties + + /// + /// Returns the handle to the block of memory. + /// + /// + /// The representing the block of memory. + /// + public SafeHGlobalHandle MemoryBlock + { + get { return _memoryBlock; } + } + + #endregion + + #region Methods + + /// + /// Performs application-defined tasks associated with freeing, + /// releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (!_memoryBlock.IsInvalid) + { + _memoryBlock.Dispose(); + _memoryBlock = _invalidBlock; + } + } + + /// + /// Ensures that there is sufficient memory allocated. + /// + /// + /// The required capacity of the block, in bytes. + /// + /// + /// There is insufficient memory to satisfy the request. + /// + public void EnsureCapacity(int capacity) + { + int currentSize = _memoryBlock.IsInvalid ? 0 : _memoryBlock.Size; + if (capacity > currentSize) + { + if (0 != currentSize) currentSize <<= 1; + if (capacity > currentSize) currentSize = capacity; + + if (!_memoryBlock.IsInvalid) _memoryBlock.Dispose(); + _memoryBlock = SafeHGlobalHandle.Allocate(currentSize); + } + } + + /// + /// Reads the Unicode string from the memory block. + /// + /// + /// The length of the string to read, in characters. + /// + /// + /// The string read from the memory block. + /// + public string ReadString(int length) + { + if (0 >= length || _memoryBlock.IsInvalid) return null; + if (length > _memoryBlock.Size) length = _memoryBlock.Size; + return Marshal.PtrToStringUni(_memoryBlock.DangerousGetHandle(), length); + } + + /// + /// Reads the string, and extracts the stream name. + /// + /// + /// The length of the string to read, in characters. + /// + /// + /// The stream name. + /// + public string ReadStreamName(int length) + { + string name = this.ReadString(length); + if (!string.IsNullOrEmpty(name)) + { + // Name is of the format ":NAME:$DATA\0" + int separatorIndex = name.IndexOf(SafeNativeMethods.StreamSeparator, 1); + if (-1 != separatorIndex) + { + name = name.Substring(1, separatorIndex - 1); + } + else + { + // Should never happen! + separatorIndex = name.IndexOf('\0'); + if (1 < separatorIndex) + { + name = name.Substring(1, separatorIndex - 1); + } + else + { + name = null; + } + } + } + + return name; + } + + #endregion + } +} diff --git a/@integrate/WCFHost.cs b/@integrate/WCFHost.cs new file mode 100644 index 0000000..51ad48d --- /dev/null +++ b/@integrate/WCFHost.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ServiceModel; +using Watchdog.WatchdogLib.Monitor; +using Watchdog.WatchdogLib.WinForms; + +namespace Watchdog +{ + /// + /// WCF Operations Contract in order to allow the command-line process + /// to communicate with the running Application's instance + /// + [ServiceContract] + public interface IWatchdog + { + #region Application State Changes + + /// + /// Starts Monitoring + /// + [OperationContract] + void StartMonitoring(); + + /// + /// Pauses Monitoring + /// + [OperationContract] + void PauseMonitoring(); + + /// + /// Restarts Monitoring + /// + [OperationContract] + void RestartMonitoring(); + + /// + /// Restarts All Monitoring + /// + [OperationContract] + void RestartAllMonitoring(); + + /// + /// Stops Monitoring + /// + [OperationContract] + void StopMonitoring(); + + /// + /// Stops All Monitoring + /// + [OperationContract] + void StopAllMonitoring(); + + /// + /// Tell the Running Application to Reload the Configuration + /// + [OperationContract] + void ReloadConfigurationNextInterval(); + + #endregion + + #region Ping + + /// + /// To Check if we can communicate with the other side + /// + /// PING_RETURN_VALUE + [OperationContract] + int Ping(); + + #endregion + } + + /// + /// Responsible for the WCF Callbacks + /// + public class WCFHost : IWatchdog + { + #region IWatchdog Members + + /// + /// Starts Monitoring + /// + public void StartMonitoring() + { + HiddenMainWindow.StartMonitoring(true, true); + } + + /// + /// Pauses Monitoring + /// + public void PauseMonitoring() + { + HiddenMainWindow.PauseMonitoring(true); + } + + /// + /// Restarts Monitoring + /// + public void RestartMonitoring() + { + HiddenMainWindow.RestartMonitoring(4, true); + } + + /// + /// Restarts All Monitoring + /// + public void RestartAllMonitoring() + { + HiddenMainWindow.RestartAllMonitoring(4, true); + } + + /// + /// Stops Monitoring + /// + public void StopMonitoring() + { + HiddenMainWindow.StopMonitoring(true, false); + } + + /// + /// Stops All Monitoring + /// + public void StopAllMonitoring() + { + HiddenMainWindow.StopAllMonitoring(true); + } + + /// + /// Tell the Running Application to Reload the Configuration + /// + public void ReloadConfigurationNextInterval() + { + App.xmlfile.ForceRefreshOnNext_ReadData = true; + } + + /// + /// Const Value to return on Ping + /// + public const int PING_RETURN_VALUE = 10; + /// + /// To Check if we can communicate with the other side + /// + /// PING_RETURN_VALUE + public int Ping() + { + return PING_RETURN_VALUE; + } + + #endregion + + #region Configuration Changes / Display Configuration Statics + + /// + /// Adds a Process from the Configuration and saves the xml + /// + /// Process Exe File name + /// Command Line Prms for Process + /// Working Directory + /// true if successfull, false otherwise + public static bool AddProcess(string ProcessExeFileNameNPath, string CommandlinePrms, string WorkingDir) + { + WatchdogConfiguration config = App.xmlfile.ReadData(false); + bool bSuccess = config.MonitoredProcesses.AddProcessExe(ProcessExeFileNameNPath, CommandlinePrms, WorkingDir); + if (bSuccess) + App.xmlfile.SaveData(); + return bSuccess; + } + + /// + /// Removes a Process from the Configuration and saves the xml + /// + /// Process Exe File name + /// Command Line Prms for Process + /// true if successfully deleted a process with matching ProcessExeFileNameNPath and CommandlinePrms + public static bool RemoveProcess(string ProcessExeFileNameNPath, string CommandlinePrms) + { + WatchdogConfiguration config = App.xmlfile.ReadData(false); + bool bSuccess = config.MonitoredProcesses.RemoveProcessExe(ProcessExeFileNameNPath, CommandlinePrms); + if (bSuccess) + App.xmlfile.SaveData(); + return bSuccess; + } + + /// + /// Adds a Service from the configuration and saves the xml + /// + /// Name of Service + /// true if sucessfull, false otherwise + public static bool AddService(string ServiceName) + { + WatchdogConfiguration config = App.xmlfile.ReadData(false); + bool bSuccess = config.MonitoredServices.AddServiceExe(ServiceName); + if (bSuccess) + App.xmlfile.SaveData(); + return bSuccess; + } + + /// + /// Removes a Service from the configuration and saves the xml + /// + /// Name of Service + /// true if sucessfull, false otherwise + public static bool RemoveService(string ServiceName) + { + WatchdogConfiguration config = App.xmlfile.ReadData(false); + bool bSuccess = config.MonitoredServices.RemoveServiceExe(ServiceName); + if (bSuccess) + App.xmlfile.SaveData(); + return bSuccess; + } + + /// + /// Returns an array of all Processes and their configurations (in a ';' seperated string) + /// + /// Process List with configuration + public static string[] ShowProcesses() + { + List ProcessList = new List(); + WatchdogConfiguration config = App.xmlfile.ReadData(false); + string s = String.Empty; + foreach (WatchdogConfiguration.ProcessExe p in config.MonitoredProcesses.ProcessExes) + { + if (String.IsNullOrEmpty(p.CommandLinePrms) && String.IsNullOrEmpty(p.WorkingDirectory)) + s = p.ProcessExeFileNameNPath; + else if (!String.IsNullOrEmpty(p.CommandLinePrms) && String.IsNullOrEmpty(p.WorkingDirectory)) + s = p.ProcessExeFileNameNPath + ";" + p.CommandLinePrms; + else if (!String.IsNullOrEmpty(p.CommandLinePrms) && !String.IsNullOrEmpty(p.WorkingDirectory)) + s = p.ProcessExeFileNameNPath + ";" + p.CommandLinePrms + ";" + p.WorkingDirectory; + else + continue; + ProcessList.Add(s); + } + return ProcessList.ToArray(); + } + + /// + /// Returns an array of all Services + /// + /// Services List + public static string[] ShowServices() + { + List ProcessList = new List(); + WatchdogConfiguration config = App.xmlfile.ReadData(false); + foreach (WatchdogConfiguration.ServiceExe s in config.MonitoredServices.ServiceExes) + ProcessList.Add(s.Name); + return ProcessList.ToArray(); + } + + #endregion + + #region Internal Statics + + private static ServiceHost _host = null; + + /// + /// Start the WCF Pipe Host (Watchdog) + /// + internal static void StartHost() + { + // Make sure it is closed + StopHost(); + _host = new ServiceHost(typeof(WCFHost), new Uri[] { new Uri("net.pipe://localhost") }); + _host.AddServiceEndpoint(typeof(IWatchdog), new NetNamedPipeBinding(), AppResx.GetString("APPLICATION_NAME_SHORT")); + + try + { + _host.Open(); + } + catch (Exception e) + { + App.log.Fatal("Unable to start WCF Pipe Host", e); + MsgBox.ShowFatalError("Unable to start WCF Pipe Host", "Pipe Host Failure", System.Windows.Forms.MessageBoxButtons.OK); + App.Current.Shutdown(); + } + } + + /// + /// Stop the WCF Pipe Host + /// + internal static void StopHost() + { + if (_host != null) + { + _host.Close(); + _host = null; + } + } + + /// + /// Called by Client to communicate with the server + /// + /// a valid IWatchDog Object or Null if error occured + internal static IWatchdog GetWatchDogClientInterface() + { + // We only should be calling this in CommandLine Mode. + if (App.State_SpecialMode_IsCommandLineSet()) + { + try + { + IWatchdog watchdoginterface = null; + ChannelFactory iwatchdogFactory = new ChannelFactory(new NetNamedPipeBinding(), new EndpointAddress(("net.pipe://localhost/" + AppResx.GetString("APPLICATION_NAME_SHORT")))); + watchdoginterface = iwatchdogFactory.CreateChannel(); + + // Try to ping the object! ~if it fails, target object doesn't exist + if (watchdoginterface.Ping() == WCFHost.PING_RETURN_VALUE) + return watchdoginterface; + } + catch (Exception) + { + // * Ignore this error we don't want to log this* + //App.log.Error("Failed to obtain WatchDogClientInterface", e); + } + } + return null; + } + + #endregion + } +} diff --git a/Assembly/AssemblyW.cs b/Assembly/AssemblyW.cs new file mode 100644 index 0000000..55d6836 --- /dev/null +++ b/Assembly/AssemblyW.cs @@ -0,0 +1,491 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using Refl = System.Reflection; +using System.Diagnostics; +using System.Reflection; + +namespace Yaulw.Assembly +{ + /// + /// Useful Functions for Retrieving General Assembly Information + /// + public static class AssemblyW + { + #region Assembly State/Type + + /// + /// Assembly State/Type + /// + public enum AssemblyST + { + Calling, + Entry, + Executing + } + + /// + /// Internal Helper Function to Get the Appropriate Assembly State/Type + /// + /// State/Type of Assembly to Retrieve + /// Thrown if invalist AssemblyST is passed in + /// Returns the Specified State/Type Assembly + private static Refl.Assembly GetAssembly(AssemblyST st) + { + switch (st) + { + case AssemblyST.Calling: + return Refl.Assembly.GetCallingAssembly(); + case AssemblyST.Entry: + return Refl.Assembly.GetEntryAssembly(); + case AssemblyST.Executing: + return Refl.Assembly.GetExecutingAssembly(); + } + throw (new ArgumentException("Invalid AssemblyST")); + } + + #endregion + + #region Common Assembly Properties + + /// + /// Returns the Name of the Assembly + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// Returns 'just the Name' of the Assembly + public static string GetAssemblyName(AssemblyST st) + { + string[] curAsmName = GetAssembly(st).FullName.Split(','); + return curAsmName[0]; + } + + /// + /// Returns the Name of the Assembly + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// Returns 'just the Name' of the Assembly + public static string GetAssemblyName(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + string[] curAsmName = asm.FullName.Split(','); + return curAsmName[0]; + } + + /// + /// Returns the Assembly Title + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// Returns the Assembly Title, as specified in AssemblyInfo + public static string GetAssemblyTitle(AssemblyST st) + { + object[] attributes = GetAssembly(st).GetCustomAttributes(typeof(AssemblyTitleAttribute), false); + if (attributes.Length > 0) + { + AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0]; + return titleAttribute.Title; + } + return String.Empty; + } + + /// + /// Returns the Assembly Title + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// Returns the Assembly Title, as specified in AssemblyInfo + public static string GetAssemblyTitle(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + object[] attributes = asm.GetCustomAttributes(typeof(AssemblyTitleAttribute), false); + if (attributes.Length > 0) + { + AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0]; + return titleAttribute.Title; + } + return String.Empty; + } + + /// + /// Returns the Description of the Assembly + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// Returns the Assembly Description, as specified in AssemblyInfo + public static string GetAssemblyDescription(AssemblyST st) + { + object[] attributes = GetAssembly(st).GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false); + if (attributes.Length > 0) + { + AssemblyDescriptionAttribute descriptionAttribute = (AssemblyDescriptionAttribute)attributes[0]; + return descriptionAttribute.Description; + } + return String.Empty; + } + + /// + /// Returns the Description of the Assembly + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// Returns the Assembly Description, as specified in AssemblyInfo + public static string GetAssemblyDescription(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + object[] attributes = asm.GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false); + if (attributes.Length > 0) + { + AssemblyDescriptionAttribute descriptionAttribute = (AssemblyDescriptionAttribute)attributes[0]; + return descriptionAttribute.Description; + } + return String.Empty; + } + + /// + /// Returns the Company Name of the Assembly + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// Returns the Assembly Company, as specified in AssemblyInfo + public static string GetAssemblyCompany(AssemblyST st) + { + object[] attributes = GetAssembly(st).GetCustomAttributes(typeof(AssemblyCompanyAttribute), false); + if (attributes.Length > 0) + { + AssemblyCompanyAttribute companyAttribute = (AssemblyCompanyAttribute)attributes[0]; + return companyAttribute.Company; + } + return String.Empty; + } + + /// + /// Returns the Company Name of the Assembly + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// Returns the Assembly Company, as specified in AssemblyInfo + public static string GetAssemblyCompany(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + object[] attributes = asm.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false); + if (attributes.Length > 0) + { + AssemblyCompanyAttribute companyAttribute = (AssemblyCompanyAttribute)attributes[0]; + return companyAttribute.Company; + } + return String.Empty; + } + + /// + /// Returns the Product Name of the Assembly + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// Returns the Assembly Product, as specified in AssemblyInfo + public static string GetAssemblyProductName(AssemblyST st) + { + object[] attributes = GetAssembly(st).GetCustomAttributes(typeof(AssemblyProductAttribute), false); + if (attributes.Length > 0) + { + AssemblyProductAttribute productAttribute = (AssemblyProductAttribute)attributes[0]; + return productAttribute.Product; + } + return String.Empty; + } + + /// + /// Returns the Product Name of the Assembly + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// Returns the Assembly Product, as specified in AssemblyInfo + public static string GetAssemblyProductName(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + object[] attributes = asm.GetCustomAttributes(typeof(AssemblyProductAttribute), false); + if (attributes.Length > 0) + { + AssemblyProductAttribute productAttribute = (AssemblyProductAttribute)attributes[0]; + return productAttribute.Product; + } + return String.Empty; + } + + /// + /// Returns the Copyright Information of the Assembly + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// Returns the Assembly Copyright, as specified in AssemblyInfo + public static string GetAssemblyCopyright(AssemblyST st) + { + object[] attributes = GetAssembly(st).GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false); + if (attributes.Length > 0) + { + AssemblyCopyrightAttribute copyrightAttribute = (AssemblyCopyrightAttribute)attributes[0]; + return copyrightAttribute.Copyright; + } + return String.Empty; + } + + /// + /// Returns the Copyright Information of the Assembly + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// Returns the Assembly Copyright, as specified in AssemblyInfo + public static string GetAssemblyCopyright(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + object[] attributes = asm.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false); + if (attributes.Length > 0) + { + AssemblyCopyrightAttribute copyrightAttribute = (AssemblyCopyrightAttribute)attributes[0]; + return copyrightAttribute.Copyright; + } + return String.Empty; + } + + /// + /// Returns the Assembly Version + /// + /// State/Type of Assembly to Retrieve + /// Thrown if invalist AssemblyST is passed in + /// Returns the Assembly Version, as specified in AssemblyInfo + public static Version GetAssemblyVersion(AssemblyST st) + { + return SpecializedAssemblyInfo.GetAssemblyNameObj(st).Version; + } + + /// + /// Returns the Assembly Version + /// + /// Assembly to Retrieve Info for + /// Thrown if invalist Assembly is passed in + /// Returns the Assembly Version, as specified in AssemblyInfo + public static Version GetAssemblyVersion(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + return SpecializedAssemblyInfo.GetAssemblyNameObj(asm).Version; + } + + /// + /// Returns the Assembly File Version + /// + /// State/Type of Assembly to Retrieve + /// Thrown if invalist AssemblyST is passed in + /// Returns the File Version, as specified by the File + public static FileVersionInfo GetAssemblyFileVersion(AssemblyST st) + { + FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(SpecializedAssemblyInfo.GetAssemblyFileNameNPath(st)); + return fvi; + } + + /// + /// Returns the Assembly File Version + /// + /// Assembly to Retrieve Info for + /// Thrown if invalist Assembly is passed in + /// Returns the File Version, as specified by the File + public static FileVersionInfo GetAssemblyFileVersion(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(SpecializedAssemblyInfo.GetAssemblyFileNameNPath(asm)); + return fvi; + } + + #endregion + + #region Specialized Assembly Information + + /// + /// Useful Functions for Retrieving Specialized Assembly Information + /// + public class SpecializedAssemblyInfo + { + /// + /// Returns the Assembly Name Object from the Assembly + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// AssemblyName object for the specified assembly + public static Refl.AssemblyName GetAssemblyNameObj(AssemblyST st) + { + return GetAssembly(st).GetName(); + } + + /// + /// Returns the Assembly Name Object from the Assembly + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// AssemblyName object for the specified assembly + public static Refl.AssemblyName GetAssemblyNameObj(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + return asm.GetName(); + } + + /// + /// Returns the Exact File Name and Path of the Assembly + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// Full File Name and Path + public static string GetAssemblyFileNameNPath(AssemblyST st) + { + Refl.Assembly asm = GetAssembly(st); + string curAsmName = asm.Location; + return curAsmName; + } + + /// + /// Returns the Exact File Name and Path of the Assembly + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// Full File Name and Path + public static string GetAssemblyFileNameNPath(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + string curAsmName = asm.Location; + return curAsmName; + } + + /// + /// Returns the File Name (without Extension) of the Assembly + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// File Name without extension + public static string GetAssemblyFileName(AssemblyST st) + { + string curAsmName = Path.GetFileNameWithoutExtension(GetAssembly(st).ManifestModule.Name); + return curAsmName; + } + + /// + /// Returns the File Name (without Extension) of the Assembly + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// File Name without extension + public static string GetAssemblyFileName(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + string curAsmName = Path.GetFileNameWithoutExtension(asm.ManifestModule.Name); + return curAsmName; + } + + /// + /// Returns the Path of the Assembly + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// Path + public static string GetAssemblyPath(AssemblyST st) + { + Refl.Assembly asm = GetAssembly(st); + string curAsmName = Path.GetDirectoryName(asm.Location); + return curAsmName; + } + + /// + /// Returns the Path of the Assembly + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// Path + public static string GetAssemblyPath(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + string curAsmName = Path.GetDirectoryName(asm.Location); + return curAsmName; + } + + /// + /// Returns the File Name (with Extension) of the Assembly + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// File Name with extension + public static string GetAssemblyFileNameWithExtension(AssemblyST st) + { + string curAsmName = Path.GetFileName(GetAssembly(st).ManifestModule.Name); + return curAsmName; + } + + /// + /// Returns the File Name (with Extension) of the Assembly + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// File Name with extension + public static string GetAssemblyFileNameWithExtension(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + string curAsmName = Path.GetFileName(asm.ManifestModule.Name); + return curAsmName; + } + + /// + /// Returns all the Resource names inside the assembly + /// + /// State/Type of Assembly to Retrieve Info for + /// Thrown if invalid AssemblyST is passed in + /// Returns all resource names defined in the assembly + public static string[] GetAssemblyResourceNames(AssemblyST st) + { + string[] resources = GetAssembly(st).GetManifestResourceNames(); + return resources; + } + + /// + /// Returns all the Resource names inside the assembly + /// + /// Assembly to Retrieve Info for + /// Thrown if invalid Assembly is passed in + /// Returns all resource names defined in the assembly + public static string[] GetAssemblyResourceNames(Refl.Assembly asm) + { + if (asm == null) + throw new ArgumentException("Invalid Assembly"); + + string[] resources = asm.GetManifestResourceNames(); + return resources; + } + } + + #endregion + } +} diff --git a/Components/ICSharpCode.SharpZipLib.dll b/Components/ICSharpCode.SharpZipLib.dll new file mode 100644 index 0000000000000000000000000000000000000000..fe643ebc638dfc94f1149528d0e9a91ce52dc353 GIT binary patch literal 200704 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

M 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 + /// + /// + public static class TextEncryptHash + { + private static byte[] _byteky = new byte[] {173,61,94,8,43,151,9,42,113,122,47,208,230,63,122,252,134,18,73,248,232,72,122,240,145,72,249,199,16,152,236,174,111,76,119,52,161,81,161,14,11,164,65,49,127,118,100,223,177,104,145,216,39,213,239,81,143,20,111,239,35,75,167,117}; + + public static string EncryptText(string text) + { + + using (HMACSHA1 provider = new System.Security.Cryptography.HMACSHA1(_byteky, true)) + { + //provider.En + } + + return ""; + } + + } +} diff --git a/File/CHMFile.cs b/File/CHMFile.cs new file mode 100644 index 0000000..18f3660 --- /dev/null +++ b/File/CHMFile.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Resources; +using IO = System.IO; +using System.Windows.Forms; +using System.Windows; +using System.Windows.Interop; +using Yaulw.Process; +using Diag = System.Diagnostics; + +namespace Yaulw.File +{ + /// + /// CHM Help File Wrapper + /// + public class CHMFile + { + #region Private Members + + /// + /// The Full File Name and Path to the CHM File + /// + private string _FileNameNPath = ""; + + /// + /// The File Stream that created the CHM File (if used) + /// + private IO.Stream _FileStream = null; + + /// + /// Internally we keep track of each Form to Help Context ID + /// + private Dictionary s_FormContextMap = new Dictionary(); + + #endregion + + #region Construction + + /// + /// Construct a CHMFile object with the specified chm file + /// + /// a full path to a valid chm file + public CHMFile(string FileNameNPath) + { + if (!String.IsNullOrEmpty(FileNameNPath) && IO.File.Exists(FileNameNPath) && (IO.Path.GetExtension(FileNameNPath).ToLower() == "chm")) + _FileNameNPath = FileNameNPath; + else + throw new ArgumentException("Invalid chm File or File does not exist"); + } + + /// + /// Construct a CHMFile object from the specified stream (will construct a chm file in + /// a temporary location from the specified stream) + /// + /// stream to construct the chm file from + /// The name of the application (will be used when creating the .chm file) + /// if true, will force a new file to be written out, false will only write file if not exists + public CHMFile(IO.Stream stream, string ApplicationName, bool ForceNewFile) + { + if (stream != null && !String.IsNullOrEmpty(ApplicationName)) + { + _FileStream = stream; + _FileNameNPath = IO.Path.GetTempPath() + ApplicationName + ".chm"; + EnsureCHMFileExistance(ForceNewFile); + } + else + throw new ArgumentNullException("Resource can not be null. ApplicationName can not be empty"); + } + + #endregion + + #region Public Consts + + /// + /// Legacy MFC View Context offset + /// + public const int MNU = 0x10000; + + /// + /// Legacy MFC Dialog Context offset + /// + public const int DLG = 0x20000; + + #endregion + + #region Public Methods + + /// + /// Attaches the HelpRequest Handler as well as shows the HelpButton on the CaptionBar + /// + /// WinForm to Attach Help Button to + /// the Help Topic Context Id to attach to the specified WinForm + public void AttachHelpCaptionButtonToWinForm(Form form, int dwContextId) + { + // Set the HelpButton to be displayed + form.MinimizeBox = false; + form.MaximizeBox = false; + form.HelpButton = true; + + // add the form / contextID to the map + s_FormContextMap[form] = (int)dwContextId; + + // Subscribe to the Help Requested Handler + form.HelpRequested += new HelpEventHandler(form_HelpRequested); + + // Subscribe to the Destroyed Event Handler *to remove the form from the list* + form.HandleDestroyed += new EventHandler(form_HandleDestroyed); + } + + /// + /// Launch CHM Help + /// + public void LaunchHelp() + { + int bHelpIsAlreadyRunning_Pid = 0; + Diag.Process[] ps = ProcessW.AllRunningProcessesOf("hh", false, true); + if (ps != null && ps.Length >= 1) + { + foreach (Diag.Process p in ps) + { + string ProcessName = ""; + string CommandLine = ""; + ProcessW.GetCommandLineArgumentsForProcessPID(p.Id, out ProcessName, out CommandLine); + if (CommandLine.Contains(_FileNameNPath)) + { + bHelpIsAlreadyRunning_Pid = p.Id; + break; + } + } + } + + // No need to launch a second time + if (bHelpIsAlreadyRunning_Pid != 0) + { + // Set Focus + IntPtr hWnd = Yaulw.Win32.Functions.GetFirstTopLevelWindowForProcess(bHelpIsAlreadyRunning_Pid); + if (hWnd != IntPtr.Zero) + Yaulw.Win32.User32.SetActiveWindow(hWnd); + } + else + { + // Launch + EnsureCHMFileExistance(false); + PStarter.StartProcess(PStartInfo.CreateProcess(_FileNameNPath, "", "", false, System.Diagnostics.ProcessWindowStyle.Normal, true), false, false); + } + } + + /// + /// Launch CHM Help + /// + /// form which will be the parent of the CHM File + public void LaunchHelp(Form form) + { + if (form != null) + { + Control control = Control.FromHandle(form.Handle); + if (control != null) + { + EnsureCHMFileExistance(false); + Help.ShowHelp(control, _FileNameNPath); + } + } + } + + /// + /// aunch CHM Help + /// + /// Window which will be the parent for the CHM file + public void LaunchHelp(Window window) + { + if (window != null) + { + Control control = Control.FromHandle(GetHandleForWPFWindow(window)); + if (control != null) + { + EnsureCHMFileExistance(false); + Help.ShowHelp(control, _FileNameNPath); + } + } + } + + /// + /// Launch CHM Help for the specified Context + /// + /// form which will be the parent of the CHM File + /// Context Id to launch chm file with + public void LaunchHelp(Form form, int dwContextId) + { + if (form != null) + { + Control control = Control.FromHandle(form.Handle); + if (control != null) + { + EnsureCHMFileExistance(false); + Help.ShowHelp(control, _FileNameNPath, HelpNavigator.TopicId, dwContextId.ToString()); + } + } + } + + /// + /// aunch CHM Help for the specified Context + /// + /// Window which will be the parent for the CHM file + /// Context Id to launch chm file with + public void LaunchHelp(Window window, int dwContextId) + { + if (window != null) + { + EnsureCHMFileExistance(false); + Help.ShowHelp(null, _FileNameNPath, HelpNavigator.TopicId, dwContextId.ToString()); + } + } + + #endregion + + #region Private WinForm Help Event Handlers + + /// + /// Handle HelpRequests + /// + private void form_HelpRequested(object sender, HelpEventArgs hlpevent) + { + Form form = sender as Form; + if (form != null) + { + int dwContextId = s_FormContextMap[form]; + Help.ShowHelp(form, _FileNameNPath, HelpNavigator.TopicId, dwContextId.ToString()); + } + } + + /// + /// Handle Form Destroys + /// + private void form_HandleDestroyed(object sender, EventArgs e) + { + Form form = sender as Form; + if (form != null) + s_FormContextMap.Remove(form); + } + + #endregion + + #region Private Helpers + + /// + /// Creates the CHMFile from the stream (if a stream was used) + /// + /// true to force re-creation of the file, false otherwise + private void EnsureCHMFileExistance(bool ForceCreationOfFile) + { + if (_FileStream != null) + { + if (ForceCreationOfFile || !IO.File.Exists(_FileNameNPath)) + { + using (IO.FileStream fs = new IO.FileStream(_FileNameNPath, IO.FileMode.Create)) + { + using (IO.BinaryWriter writer = new IO.BinaryWriter(fs)) + { + // Read from the Resource + Byte[] streamData = new Byte[_FileStream.Length]; + _FileStream.Read(streamData, 0, streamData.Length); + + // Write to File + writer.Write(streamData); + writer.Close(); + } + } + } + } + } + + /// + /// Uses the InteropHelper to get the Handle of a WPF Window + /// + /// a WPF Window + /// an hWnd for the specified WPF Window + private IntPtr GetHandleForWPFWindow(Window window) + { + WindowInteropHelper InteropHelper = new WindowInteropHelper(window); + return InteropHelper.Handle; + } + + #endregion + } +} diff --git a/File/FileWriter.cs b/File/FileWriter.cs new file mode 100644 index 0000000..784b161 --- /dev/null +++ b/File/FileWriter.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace Yaulw.File +{ + /// + /// Simple Line-By-Line UTF/Ascii File Writer Object + /// + public class FileWriter + { + #region Private Members + + private string _FileName = ""; + private string _FileType = ""; + private string _dirPath = ""; + private bool _OverideExisting = false; + private bool _FileIsCreated = false; + + #endregion + + #region Construction + + /// + /// Create a Simple UTF/Ascii Line FileWriter Object + /// + /// The Name of the File (If blank will generate a random file name) + /// The Type of File to write to (Default = "log") + /// The path where to write the file (If Blank will use Temp Path) + /// true to overide an existing file, false will try to append by default + public FileWriter(string FileName = "", string FileType = "log", string dirPath = "", bool OverideExisting = true) + { + _FileName = FileName; + _FileType = FileType; + _dirPath = dirPath; + _OverideExisting = OverideExisting; + + // Generate File Name and Path, if not exist + if(String.IsNullOrEmpty(_FileName)) + _FileName = System.IO.Path.GetFileNameWithoutExtension(System.IO.Path.GetRandomFileName()); + if(String.IsNullOrEmpty(_dirPath)) + _dirPath = System.IO.Path.GetTempPath(); + + // Make Sure Path Exists + if (!Directory.Exists(_dirPath)) + Directory.CreateDirectory(_dirPath); + } + + #endregion + + #region Write Ascii + + /// + /// Write a Line in Ascii to File + /// + /// ascii line to write + public void WriteLineA(string line) + { + using (FileStream fs = CreateFileStream()) + using(StreamWriter sw = new StreamWriter(fs, Encoding.ASCII)) + { + sw.WriteLine(line); + sw.Flush(); + } + } + + /// + /// Write Lines in Ascii to file + /// + /// ascii lines to write + public void WriteLineA(string[] lines) + { + using (FileStream fs = CreateFileStream()) + using (StreamWriter sw = new StreamWriter(fs, Encoding.ASCII)) + { + foreach (string line in lines) + sw.WriteLine(line); + sw.Flush(); + } + } + + #endregion + + #region Write UTF + + /// + /// Write a Line in UTF to File + /// + /// utf line to write + public void WriteLineUTF8(string line) + { + using (FileStream fs = CreateFileStream()) + using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8)) + { + sw.WriteLine(line); + sw.Flush(); + } + } + + /// + /// Write Lines in UTF to File + /// + /// utf lines to write + public void WriteLineUTF8(string[] lines) + { + using (FileStream fs = CreateFileStream()) + using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8)) + { + foreach (string line in lines) + sw.WriteLine(line); + sw.Flush(); + } + } + + #endregion + + #region Public Properties + + /// + /// Returns the File Name + /// + public string FileName { get { return (this._FileName + "." + this._FileType); } } + + /// + /// Returns the Path where the File is located + /// + public string Path { get { return (this._dirPath); } } + + /// + /// Returns both Path and FileName + /// + public string FileNameNPath { get { return (this.Path + "\\" + this.FileName); } } + + #endregion + + #region Public Methods + + /// + /// Deletes the File, if it exists + /// + /// true if successful, false otherwise + public bool DeleteFile() + { + try + { + if (System.IO.File.Exists(this.FileNameNPath)) + { + System.IO.File.Delete(this.FileNameNPath); + _FileIsCreated = false; + return true; + } + } + catch (Exception) { /* ignore */ } + return false; + } + + #endregion + + #region Private Methods + + /// + /// Creates the File Stream, either in Create or Append Mode + /// + /// a File Stream to Write to * Must be Closed by Caller * + private FileStream CreateFileStream() + { + try + { + bool bFileExists = System.IO.File.Exists(this.FileNameNPath); + if (!_FileIsCreated && _OverideExisting && bFileExists) + { + _FileIsCreated = true; + return (new FileStream(this.FileNameNPath, FileMode.Create)); + } + else if (bFileExists) + { + return (new FileStream(this.FileNameNPath, FileMode.Append)); + } + else + { + _FileIsCreated = true; + return (new FileStream(this.FileNameNPath, FileMode.Create)); + } + } + catch (Exception) { /* ignore */ } + return null; + } + + #endregion + } +} diff --git a/File/INIFile.cs b/File/INIFile.cs new file mode 100644 index 0000000..a994a73 --- /dev/null +++ b/File/INIFile.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using Yaulw.Tools; +using Yaulw.Win32; +using System.Diagnostics; + +namespace Yaulw.File +{ + /// + /// Responsible for Reading and Writing an INI File. + /// Simply construct an INIFile Object passing in a File name and Path, + /// as well as an enum that lists all the Categories and Keys. + /// + /// An example *You can mix and match Categories and Keys as needed *: + /// public enum INICatsNKeys + /// { + /// App_Settings__Load_XML_At_StartUp, + /// App_Settings__Allow_Command_Line_to_override_INI, + /// App_Settings__Default_User, + /// App_Settings__Default_Server, + /// Runtime_Debug__Error_Debug_Level, + /// Data_Access__Read_Only, + /// Settings__Program_Folder, + /// Settings__User1_Default, + /// Recent_File_List__File1, + /// Recent_File_List__File2, + /// Recent_File_List__File3, + /// Recent_File_List__File4, + /// } + /// + /// The '__' Character signifies a category to Key change + /// the '_' will be replaced by ' ', when actually writing to the Ini File. + /// + public class INIFile + { + #region Private Members + + private string _IniPathNFileName = String.Empty; + private Type _IniKeysNCategories = null; + + #endregion + + #region Construction + + /// + /// Initialize an INIFile object with a valid path and FileName to write/read from. + /// Also pass in an Enum Type that contains Category and Key Information. This Enum will be used + /// to read and write to the INIFile (to determine which Section/Key to read/write from/to) + /// + /// Pass in the full file name and path to use * Creates Directory, if it does not exits * + /// Pass in an Enum that specifies the Categories and Keys + /// Thrown if an invalid Enum is passed in via IniKeysNCategories + public INIFile(string IniPathNFileName, Type IniKeysNCategories) + { + // Check and ensure Enum Accuracy + if (IniKeysNCategories.IsEnum && EnumTool.EnumType_HasCategoryNKey(IniKeysNCategories)) + { + _IniKeysNCategories = IniKeysNCategories; + } + else + throw new ArgumentException("IniKeysNCategories is not an Enum or not all Enums contain Key and Category Information"); + + // Ensure Directory Existence + if (!Directory.Exists(Path.GetDirectoryName(IniPathNFileName))) + Directory.CreateDirectory(Path.GetDirectoryName(IniPathNFileName)); + _IniPathNFileName = IniPathNFileName; + } + + #endregion + + #region Public Properties + + /// + /// Ini File Used by IniFile + /// + public string IniFile { get { return _IniPathNFileName; } } + + #endregion + + #region Public Ini Key Reader / Writer Functions + + /// + /// Use this to directly set a key to any string value + /// + /// Specify Key and category set Directly + /// value to set + /// If cKey is not a valid Category/Key Pair Enum + public void SetKeyValue(Enum cKey, T Value) + { + if (EnumTool.Enum_HasCategoryNKey(cKey)) + { + string Category; + string Key; + EnumTool.Enum_ToCategoryNKey(cKey, out Category, out Key); + string ValueStr = ObjTool.ConvertObjToString(Value); + SetKeyValueString(Category, Key, ValueStr); + } + else + { + throw new ArgumentException("cKey must have both Category and Key Information"); + } + } + + /// + /// Use this to directly retrieve a key + /// + /// Specify Key and category set Directly + /// default value to return, if not found + /// value retrieved or default value + /// If cKey is not a valid Category/Key Pair Enum + public T GetKeyValue(Enum cKey, T defaultValue) + { + try + { + if (EnumTool.Enum_HasCategoryNKey(cKey)) + { + string Category; + string Key; + EnumTool.Enum_ToCategoryNKey(cKey, out Category, out Key); + + // Get Key and verify that something was found + string defaultValueStr = ObjTool.ConvertObjToString(defaultValue); + string KeyValue = GetKeyValueString(Category, Key, defaultValueStr); + bool bHasAValue = (KeyValue.Trim().Length != 0); + + // always write the value back out to ini + SetKeyValue(cKey, bHasAValue ? KeyValue : defaultValueStr); + + // Return value + if (bHasAValue) + { + return ObjTool.ConvertStringToObj(KeyValue); + } + else + { + return defaultValue; + } + } + else + { + throw new ArgumentException("cKey must have both Category and Key Information"); + } + } + catch (Exception) { /* ignore */ } + return defaultValue; + } + + #endregion + + #region Private Generic Ini Key Reader / Writer Functions + + /// + /// Set a value for the specified Key and for the specified category + /// + /// specify category + /// specify key + /// specify a value to set + private void SetKeyValueString(string category, string key, string value) + { + bool bSuccess = Kernel32.WritePrivateProfileString(category, key, value, IniFile); + Debug.Assert(bSuccess); + } + + /// + /// Retrieve the value for a specified Key and for a specified category + /// + /// specify category + /// specify key + /// specify default value, returned when no value found + /// the value for the Key, or default value if no value found + private string GetKeyValueString(string category, string key, string defaultValue) + { + StringBuilder returnString = new StringBuilder(1024); + Kernel32.GetPrivateProfileString(category, key, defaultValue, returnString, 1024, IniFile); + return returnString.ToString().Split('\0')[0]; + } + + #endregion + + #region Private Generic Ini File Readers (Not Used) + + /// + /// Use this to return all the categories in an INI File + /// + /// a list with Gategory Items, or 0 length list is none found + private List GetAllCategories() + { + StringBuilder returnString = new StringBuilder(65536); + uint returnValue = Kernel32.GetPrivateProfileString(null, null, null, returnString, 65536, IniFile); + List result = new List(returnString.ToString().Split('\0')); + result.RemoveRange(result.Count - 2, 2); + return result; + } + + /// + /// Use this to return the Keys from a category from an INI File + /// + /// a list with Key Items, or 0 length list is none found + private List GetAllKeysInCategory(string category) + { + StringBuilder returnString = new StringBuilder(32768); + Kernel32.GetPrivateProfileString(category, null, null, returnString, 32768, IniFile); + List result = new List(returnString.ToString().Split('\0')); + result.RemoveRange(result.Count - 2, 2); + return result; + } + + #endregion + } +} diff --git a/File/ISReadWrite.cs b/File/ISReadWrite.cs new file mode 100644 index 0000000..4896bbd --- /dev/null +++ b/File/ISReadWrite.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO.IsolatedStorage; +using System.IO; +using System.Reflection; + +namespace Yaulw.File +{ + /// + /// + /// + public enum ISScope + { + User, + Machine, + } + + /// + /// Class allows reading and writing to isolated Storage + /// + public class ISReadWrite + { + #region Private Members + + private string _ISOLATED_STORAGE_FILENAME = ""; + private bool _bFileInIsoStoreExists = false; + private Xml.XSerializer _XSerializer = null; + private IsolatedStorageScope _scope = IsolatedStorageScope.None; + private IsolatedStorageFile _isoStore = null; + + #endregion + + #region Construction + + /// + /// + /// + /// + public ISReadWrite(string ISFileName) : this(ISFileName, ISScope.Machine) { } + + /// + /// + /// + /// + /// + public ISReadWrite(string ISFileName, ISScope scope) + { + _ISOLATED_STORAGE_FILENAME = ISFileName; + + if (String.IsNullOrEmpty(_ISOLATED_STORAGE_FILENAME)) + throw new ArgumentNullException("ISFileName can not be empty."); +#if NET4 + if (!IsolatedStorageFile.IsEnabled) + throw new Exception("Isolated Storage not enabled"); +#endif + _scope = ConvertISScopeToIsolatedStorageScope(scope); + _isoStore = IsolatedStorageFile.GetStore(_scope, typeof(System.Security.Policy.Url), typeof(System.Security.Policy.Url)); + _bFileInIsoStoreExists = (_isoStore.GetFileNames(ISFileName).Length > 0); + _XSerializer = new Xml.XSerializer(); + } + + #endregion + + #region Public Read / Write Methods + + public string ReadFromIS() + { + using (IsolatedStorageFileStream fs = new IsolatedStorageFileStream(_ISOLATED_STORAGE_FILENAME, FileMode.OpenOrCreate, _isoStore)) + using (StreamReader reader = new StreamReader(fs)) + { + _bFileInIsoStoreExists = true; + string strFileContents = reader.ReadToEnd(); + if (!String.IsNullOrEmpty(strFileContents)) + return strFileContents; + } + return String.Empty; + } + + /// + /// + /// + /// + /// + public T ReadFromIS() + { + using (IsolatedStorageFileStream fs = new IsolatedStorageFileStream(_ISOLATED_STORAGE_FILENAME, FileMode.OpenOrCreate, _isoStore)) + using (StreamReader reader = new StreamReader(fs)) + { + _bFileInIsoStoreExists = true; + string strFileContents = reader.ReadToEnd(); + if (!String.IsNullOrEmpty(strFileContents)) + { + T t = _XSerializer.ReadFromString(strFileContents); + return t; + } + } + + return default(T); + } + + /// + /// + /// + /// + /// + public void WriteToIS(T XMLSerializableObject) where T : new() + { + using (IsolatedStorageFileStream fs = new IsolatedStorageFileStream(_ISOLATED_STORAGE_FILENAME, FileMode.Create, _isoStore)) + using (StreamWriter writer = new StreamWriter(fs)) + { + _bFileInIsoStoreExists = true; + string strFileContentsToWrite = _XSerializer.WriteToString(XMLSerializableObject); + if(!String.IsNullOrEmpty(strFileContentsToWrite)) + writer.Write(strFileContentsToWrite); + } + } + + /// + /// + /// + public void WriteToIS(string Content) + { + using (IsolatedStorageFileStream fs = new IsolatedStorageFileStream(_ISOLATED_STORAGE_FILENAME, FileMode.Create, _isoStore)) + using (StreamWriter writer = new StreamWriter(fs)) + { + _bFileInIsoStoreExists = true; + string strFileContentsToWrite = Content; + if (!String.IsNullOrEmpty(strFileContentsToWrite)) + writer.Write(strFileContentsToWrite); + } + } + + #endregion + + #region Public Methods + + /// + /// + /// + public void Delete() + { + if (_bFileInIsoStoreExists) + { + _isoStore.DeleteFile(_ISOLATED_STORAGE_FILENAME); + _bFileInIsoStoreExists = false; + } + } + + #endregion + + #region Public Properties + + public bool Exists + { + get { return _bFileInIsoStoreExists; } + } + + #endregion + + #region Private Helpers + + /// + /// + /// + /// + /// + private IsolatedStorageScope ConvertISScopeToIsolatedStorageScope(ISScope scope) + { + switch (scope) + { + case ISScope.User: + return IsolatedStorageScope.User | IsolatedStorageScope.Domain | IsolatedStorageScope.Assembly; + + case ISScope.Machine: + return IsolatedStorageScope.Machine | IsolatedStorageScope.Domain | IsolatedStorageScope.Assembly; + } + return IsolatedStorageScope.Application; + } + + #endregion + } + +} diff --git a/File/LineReplacer.cs b/File/LineReplacer.cs new file mode 100644 index 0000000..9f11c19 --- /dev/null +++ b/File/LineReplacer.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using IO = System.IO; + +namespace Yaulw.File +{ + #region Rule Definitions + + /// + /// Comparer Modifier used when looking at the line + /// + public enum LineReplace_ComparerModifier + { + None, + toLower, + toUpper, + } + + /// + /// Rule Used to determine which line to replace + /// ~Replace Line with contains the new line to put instead + /// + public struct LineReplace_Rule + { + public string Contains; + public string StartsWith; + public string EndsWith; + public LineReplace_ComparerModifier Comparer; + public string ReplaceLineWith; + } + + #endregion + + /// + /// Useful for replacing lines in a text file. + /// It reads in a text file line by line and compares the lines. + /// Using the rules you can replace a line or multiple lines and write out to the text file. + /// + public class LineReplacer + { + #region Private Members + + private Dictionary _RulesDictionary = new Dictionary(); + private string _fileNameNPath = ""; + private Encoding _encoding = Encoding.UTF8; + + #endregion + + #region Construction + + /// + /// Allows for easy line replacing of a text file + /// + /// Full File Name and Path to a file that exists + /// Thrown when File does not exist + public LineReplacer(string FileNameNPath, Encoding encoding) + { + if (String.IsNullOrEmpty(FileNameNPath) || !IO.File.Exists(FileNameNPath)) + throw new ArgumentException("Line Replace makes only sense on Files that exist already"); + + _fileNameNPath = FileNameNPath; + _encoding = encoding; + } + + #endregion + + #region Public Methods + + /// + /// Add/Update Line Replacing Rules into here BEFORE calling ReplaceLines() + /// + /// unique name of the rule + /// Rule Definition + /// true if successfully added, false otherwise + public bool AddUpdateRule(string RuleName, LineReplace_Rule rule) + { + if (!String.IsNullOrEmpty(RuleName)) + { + // We must be able to match on something + if (String.IsNullOrEmpty(rule.StartsWith) && String.IsNullOrEmpty(rule.EndsWith) && + String.IsNullOrEmpty(rule.Contains)) + return false; + + // We must be able to replace with something + if (String.IsNullOrEmpty(rule.ReplaceLineWith)) + return false; + + // Rule is good, add it + _RulesDictionary[RuleName] = rule; + } + return false; + } + + /// + /// Call this Function to Replace all the Lines of the Text Files that + /// match a specific Rule + /// + public void ReplaceLines() + { + if (_RulesDictionary.Count <= 0) + return; + + // # Readin and Process all Lines + List ReadInLines = new List(); + using (IO.FileStream fs = new IO.FileStream(_fileNameNPath, IO.FileMode.Open)) + using (IO.StreamReader reader = new IO.StreamReader(fs, _encoding)) + { + // # Iterate thru all the lines in the File + for (string Line = reader.ReadLine(); Line != null; Line = reader.ReadLine()) + { + FoundRuleMathAndPerformedAction(ref Line); + ReadInLines.Add(Line); + } + } + + // # Write out all the Lines + using (IO.FileStream fs = new IO.FileStream(_fileNameNPath, IO.FileMode.Create)) + using (IO.StreamWriter writer = new IO.StreamWriter(fs, _encoding)) + { + foreach (string Line in ReadInLines) + writer.WriteLine(Line); + } + } + + #endregion + + #region Private Helpers + + /// + /// Responsible for finding a match between a line and a rule and + /// performing the action + /// + /// Line to find a match for + /// true, if match was found/action was done, false otherwise + private bool FoundRuleMathAndPerformedAction(ref string Line) + { + if (String.IsNullOrEmpty(Line)) + return false; + + bool bFoundMatch = false; + // Iterate foreach rule for a line + foreach (LineReplace_Rule rule in _RulesDictionary.Values) + { + // Make sure to use the proper comparer Modifier for the rule + string line = Line; + if (rule.Comparer == LineReplace_ComparerModifier.toLower) + line = Line.ToLower(); + else if (rule.Comparer == LineReplace_ComparerModifier.toUpper) + line = Line.ToUpper(); + + // Now let's match on the actual Rule + if (!bFoundMatch && !String.IsNullOrEmpty(rule.StartsWith)) + bFoundMatch = line.StartsWith(rule.StartsWith); + if (!bFoundMatch && !String.IsNullOrEmpty(rule.EndsWith)) + bFoundMatch = line.StartsWith(rule.EndsWith); + if (!bFoundMatch && !String.IsNullOrEmpty(rule.Contains)) + bFoundMatch = line.StartsWith(rule.Contains); + if (bFoundMatch) + { + Line = rule.ReplaceLineWith; + break; + } + } + return bFoundMatch; + } + + #endregion + + } +} diff --git a/File/LoggerQuick.cs b/File/LoggerQuick.cs new file mode 100644 index 0000000..91a4639 --- /dev/null +++ b/File/LoggerQuick.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using Yaulw.Assembly; + +namespace Yaulw.File +{ + /// + /// Easy Class for quick and dirty logging *that can automatically on a daily basis* + /// + public class LoggerQuick + { + #region Private Members + + /// + /// FileWriter Object + /// + private Yaulw.File.FileWriter _fileWriter = null; + + /// + /// Current Directory + /// + private string _curPath = ""; + + /// + /// Log Debug Messages + /// + private bool _LogDebug = false; + + /// + /// Name of the Log (also serves as RegKey) + /// + private string LogName = ""; + + #endregion + + #region Construction + + /// + /// Quick Logger (which can clear itself daily and has no dependencies) + /// quick n' dirty logs in the current directory from which it is called + /// + /// Name of the Log file + /// true to spit out DebugMessages, false otherwise + public LoggerQuick(string LogName, bool bLogDebugMessages) + { + if(String.IsNullOrEmpty(LogName)) + throw new ArgumentException("LogName can't be empty"); + + // Log Name, which will also server as the registry key name + this.LogName = Path.GetFileName(LogName); + + // Log Debug Messages? + _LogDebug = bLogDebugMessages; + + // Get the current running directory + if (String.IsNullOrEmpty(_curPath)) + _curPath = Path.GetDirectoryName(AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Executing)); + + // Set up FileWriter + if (_fileWriter == null) + _fileWriter = new Yaulw.File.FileWriter(LogName, "log", _curPath, false); + } + + /// + /// Quick Logger (which can clear itself daily and has no dependencies) + /// quick n' dirty logs in the current directory from which it is called + /// + /// Name of the Log file + /// true to spit out DebugMessages, false otherwise + /// path to log to + public LoggerQuick(string LogName, bool bLogDebugMessages, string path) + { + if (String.IsNullOrEmpty(LogName)) + throw new ArgumentException("LogName can't be empty"); + + // Log Name, which will also server as the registry key name + this.LogName = Path.GetFileName(LogName); + + // Log Debug Messages? + _LogDebug = bLogDebugMessages; + + // make sure path is valid + try + { + if (!String.IsNullOrEmpty(path)) + { + path = Path.GetDirectoryName(path); + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + } + } + catch (Exception) { /* ignore */ } + + // Get the current running directory + if (String.IsNullOrEmpty(_curPath) && String.IsNullOrEmpty(path)) + _curPath = Path.GetDirectoryName(AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Executing)); + else + _curPath = path; + + // Set up FileWriter + if (_fileWriter == null) + _fileWriter = new Yaulw.File.FileWriter(LogName, "log", _curPath, false); + } + + #endregion + + #region Public Log Methods + + /// + /// Debug Logging only logs when _LogDebug is set + /// + /// + /// + public void Debug(string line, params object[] args) + { + if (_LogDebug) + Log("Debug: ", line, args); + } + + /// + /// Info Logging + /// + /// + /// + public void Info(string line, params object[] args) + { + Log("Info: ", line, args); + } + + /// + /// Error Logging + /// + /// + /// + public void Error(string line, params object[] args) + { + Log("Error: ", line, args); + } + + #endregion + + #region Private Log Methods + + /// + /// Used for logging * Medisoft people * crack me up + /// + /// + /// + private void Log(string prePrend, string line, params object[] args) + { + if (_fileWriter != null) + { + // Clear the log once a day automatically - yeah! + DateTime LastLogged = Yaulw.Registry.RegKey.GetKey("LoggerQuick", LogName, DateTime.MinValue); + if (LastLogged != DateTime.MinValue && LastLogged.Day != DateTime.Now.Day) + _fileWriter.DeleteFile(); + + // Always set the DT Stamp, for every log + Yaulw.Registry.RegKey.SetKey("LoggerQuick", LogName, DateTime.Now); + + // Now let's start Logging + line = prePrend + line; + if (args != null) + _fileWriter.WriteLineA(String.Format(line, args)); + else + _fileWriter.WriteLineA(line); + } + } + + #endregion + + } +} diff --git a/File/Logging.cs b/File/Logging.cs new file mode 100644 index 0000000..1a35c6a --- /dev/null +++ b/File/Logging.cs @@ -0,0 +1,391 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using Diag = System.Diagnostics; + +// Log4Net Declarations +using log4net.Appender; +using log4net.Core; +using log4net.Layout; +using log4net.Config; +using log4net; +using Yaulw.Other; + +namespace Yaulw.File +{ + /// + /// Logging Detail + /// + public enum Logging_Detail + { + NONE, + ERROR, + INFO, + DEBUG + } + + /// + /// Used to Configure a Logger Instance in the Logging Class via AddGlobalLoggerConfiguration() + /// + /// + /// Logging_Configuration config; + /// config.LogFileNameNPath = "c:\\LogFile.log"; + /// config.Detail = Logging_Detail.Error; + /// config.UseExclusiveFileLock = false; + /// config.maxFileSizeInMB = 4; + /// config.numOfBackupLogFiles = 3; + /// config.Log4NetDetailPatternLayout = "%date{dd MMM HH:mm:ss,fff} [%thread] %level - %message%newline"; (Default) + /// "%date{dd MMM yyyy HH:mm:ss,fff} [%thread] %level %logger - %message%newline"; + /// config.LogCallingTypeAndCallingFunction = true; + /// + /// + /// + public struct Logging_Configuration + { + public string LogFileNameNPath; + public Logging_Detail Detail; + public bool UseExclusiveFileLock; + public int maxFileSizeInMB; + public int numOfBackupLogFiles; + public string Log4NetDetailPatternLayout; + public bool LogCallingType; + public bool LogCallingFunction; + } + + /// + /// Is a Wrapper Object around Log4Net's Rolling File Appender. + /// Use it by calling AddGlobalLoggerConfiguration() with a valid Logging Configuration. + /// You can configure multipe Logger Instances distinguished by Name. + /// subsequent calls can call GetLogger() with the named Logger instance to receive a valid logger object + /// + public class Logging + { + #region Private Static Members + + private static Dictionary _loggerConfigurationMap = new Dictionary(); + private static Dictionary _loggerObjects = new Dictionary(); + + // If this process is hosted in VS then it changes the stack frame + private static bool s_IsVSHosted = false; + + #endregion + + #region Private Construction + + /// + /// Private Constructor should only be called by static GetLogger() Function + /// + /// if true, will log calling type and Calling Function + private Logging(bool bLogCallingType, bool bLogCallingFunction) + { + s_IsVSHosted = Diag.Process.GetCurrentProcess().ProcessName.Contains("vshost"); + _LogCallingType = bLogCallingType; + _LogCallingFunction = bLogCallingFunction; + } + + #endregion + + #region Internal Members + + // Initialized by GetLogger() + internal ILog _Log4NetLog = null; + + // Initialized by GetLogger() + internal bool _LogCallingType = false; + + // Initialized by GetLogger() + internal bool _LogCallingFunction = false; + + #endregion + + #region Public Log Methods + + /// + /// Log Information + /// + /// Message to write + /// arguments to pass to String.Format + public void Info(string message, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Info(MessageHeader() + String.Format(message, args)); } } + + /// + /// Log Information + /// + /// Message to write + /// Use this to add/substract from the base stack level you want to retrieve + /// arguments to pass to String.Format + public void Info(string message, int nPlusMinus, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Info(MessageHeader(nPlusMinus) + String.Format(message, args)); } } + + /// + /// Log Information + /// + /// Message to write + /// Exception to Log + /// arguments to pass to String.Format + public void Info(string message, Exception exception, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Info(MessageHeader() + String.Format(message, args), exception); } } + + /// + /// Log Information + /// + /// Message to write + /// Exception to Log + /// Use this to add/substract from the base stack level you want to retrieve + /// arguments to pass to String.Format + public void Info(string message, Exception exception, int nPlusMinus, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Info(MessageHeader(nPlusMinus) + String.Format(message, args), exception); } } + + /// + /// Log Debug Information + /// + /// Message to write + /// arguments to pass to String.Format + public void Debug(string message, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Debug(MessageHeader() + String.Format(message, args)); } } + + /// + /// Log Debug Information + /// + /// Message to write + /// Use this to add/substract from the base stack level you want to retrieve + /// arguments to pass to String.Format + public void Debug(string message, int nPlusMinus, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Debug(MessageHeader(nPlusMinus) + String.Format(message, args)); } } + + /// + /// Log Debug Information + /// + /// Message to write + /// Exception to Log + /// arguments to pass to String.Format + public void Debug(string message, Exception exception, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Debug(MessageHeader() + String.Format(message, args), exception); } } + + /// + /// Log Debug Information + /// + /// Message to write + /// Exception to Log + /// Use this to add/substract from the base stack level you want to retrieve + /// arguments to pass to String.Format + public void Debug(string message, Exception exception, int nPlusMinus, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Debug(MessageHeader(nPlusMinus) + String.Format(message, args), exception); } } + + /// + /// Log Error Information + /// + /// Message to write + /// arguments to pass to String.Format + public void Error(string message, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Error(MessageHeader() + String.Format(message, args)); } } + + /// + /// Log Error Information + /// + /// Message to write + /// Use this to add/substract from the base stack level you want to retrieve + /// arguments to pass to String.Format + public void Error(string message, int nPlusMinus, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Error(MessageHeader(nPlusMinus) + String.Format(message, args)); } } + + /// + /// Log Error Information + /// + /// Message to write + /// Exception to Log + /// arguments to pass to String.Format + public void Error(string message, Exception exception, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Error(MessageHeader() + String.Format(message, args), exception); } } + + /// + /// Log Error Information + /// + /// Message to write + /// Exception to Log + /// Use this to add/substract from the base stack level you want to retrieve + /// arguments to pass to String.Format + public void Error(string message, Exception exception, int nPlusMinus, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Error(MessageHeader(nPlusMinus) + String.Format(message, args), exception); } } + + /// + /// Log Fatal Error Information + /// + /// Message to write + /// arguments to pass to String.Format + public void Fatal(string message, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Fatal(MessageHeader() + String.Format(message, args)); } } + + /// + /// Log Fatal Error Information + /// + /// Message to write + /// Use this to add/substract from the base stack level you want to retrieve + /// arguments to pass to String.Format + public void Fatal(string message, int nPlusMinus, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Fatal(MessageHeader(nPlusMinus) + String.Format(message, args)); } } + + /// + /// Log Fatal Error Information + /// + /// Message to write + /// Exception to Log + /// arguments to pass to String.Format + public void Fatal(string message, Exception exception, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Fatal(MessageHeader() + String.Format(message, args), exception); } } + + /// + /// Log Fatal Error Information + /// + /// Message to write + /// Exception to Log + /// Use this to add/substract from the base stack level you want to retrieve + /// arguments to pass to String.Format + public void Fatal(string message, Exception exception, int nPlusMinus, params object[] args) { if (_Log4NetLog != null) { _Log4NetLog.Fatal(MessageHeader(nPlusMinus) + String.Format(message, args), exception); } } + + /// + /// Message Header to be shown on every log message + /// + /// Use this to add/substract from the base stack level you want to retrieve + private string MessageHeader(int nPlusMinus = 0) + { + // When Running this from via VS it behaves differently then when + // running it outside of it, when running it regularly, each foreach loop + if (s_IsVSHosted) + { + if (_LogCallingType && _LogCallingFunction) + return StackWalker.GetTypeFromStack(nPlusMinus + 1) + " - " + StackWalker.GetMethodNameFromStack(nPlusMinus + 1) + "()- "; + else if (_LogCallingFunction) + return StackWalker.GetMethodNameFromStack(nPlusMinus + 1) + "()- "; + else + return ""; + } + else + { + if (_LogCallingType && _LogCallingFunction) + return StackWalker.GetTypeFromStack(nPlusMinus) + " - " + StackWalker.GetMethodNameFromStack(nPlusMinus) + "()- "; + else if (_LogCallingFunction) + return StackWalker.GetMethodNameFromStack(nPlusMinus) + "()- "; + else + return ""; + } + } + + #endregion + + #region Public Static Configuration and Logger Creation Methods + + /// + /// Used to add a new Configuration and Logger Instance onto a Static Map. + /// Will create one logger instance per unique name. + /// + /// a name for the logger instance + /// a valid configuration to use on the instance + /// a logging object that can be used to log + public static Logging AddGlobalLoggerConfiguration(string Name, Logging_Configuration Configuration) + { + // Must have a Valid Input + if (string.IsNullOrEmpty(Name)) + throw new ArgumentException("Name Is Invalid"); + + if (!_loggerObjects.Keys.Contains(Name.ToLower())) + { + // Create the Repository + log4net.Repository.ILoggerRepository repository = LogManager.CreateRepository(Name.ToLower()); + + // Create FileAppender Configuration + RollingFileAppender appender = RollingFileAppenderCreator(Configuration); + + // Run the Configuration against the Repository + BasicConfigurator.Configure(repository, appender); + + // Add Configuration to our Static Map + _loggerConfigurationMap[Name.ToLower()] = Configuration; + + // Last, but not least, Create the new Logging Instance Object and Store it + _loggerObjects[Name.ToLower()] = LogManager.GetLogger(Name.ToLower(), Name.ToLower()); + } + + // Let the Caller get the Logging Object + return GetLogger(Name); + } + + /// + /// Used to retrieve a named logging instance that has already been created via a previous call + /// to AddGlobalLoggerConfiguration(). + /// + /// a name for a previously created Logging instance + /// a Logging object that can be used to log + public static Logging GetLogger(string Name) + { + if (_loggerObjects.Keys.Contains(Name.ToLower()) && _loggerConfigurationMap.Keys.Contains(Name.ToLower())) + { + bool bLogCallingType = _loggerConfigurationMap[Name.ToLower()].LogCallingType; + bool bLogCallingFunction = _loggerConfigurationMap[Name.ToLower()].LogCallingFunction; + Logging logger = new Logging(bLogCallingType, bLogCallingFunction); + logger._Log4NetLog = _loggerObjects[Name.ToLower()]; + return logger; + } + else + throw new ArgumentException("Must call AddGlobalLoggerConfiguration() with a Configuration Before calling this Function"); + } + + #endregion + + #region Private Static Helper Methods + + /// + /// Creates a Log4Net RollingFileAppender with the specified configuration + /// + /// a valid configuration + /// a Log4Net RollingFileAppender Object + private static RollingFileAppender RollingFileAppenderCreator(Logging_Configuration config) + { + #region Input Validation + + if (config.maxFileSizeInMB <= 0) + throw new ArgumentException("Logging_Configuration - Invalid maxFileSizeInMB"); + + if (config.numOfBackupLogFiles < 0) + throw new ArgumentException("Logging_Configuration - Invalid numOfBackupLogFiles"); + + if (String.IsNullOrEmpty(config.LogFileNameNPath)) + throw new Exception("Logging_Configuration - Invalid LogFileNameNPath"); + + if (!Directory.Exists(Path.GetDirectoryName(config.LogFileNameNPath))) + Directory.CreateDirectory(Path.GetDirectoryName(config.LogFileNameNPath)); + + #endregion + + // Create and Set Layout for FileAppender + RollingFileAppender rfAppender = new RollingFileAppender(); + + // Set Layout + if (!String.IsNullOrEmpty(config.Log4NetDetailPatternLayout)) + rfAppender.Layout = new PatternLayout(config.Log4NetDetailPatternLayout); + else + rfAppender.Layout = new PatternLayout("%date{dd MMM HH:mm:ss,fff} [%thread] %level - %message%newline"); + + // Locking Minimal allows us to run Log4Net from multiple processes + if (config.UseExclusiveFileLock) + rfAppender.LockingModel = new FileAppender.ExclusiveLock(); + else + rfAppender.LockingModel = new FileAppender.MinimalLock(); + + // Configure FileName and always set Appent to true + rfAppender.File = config.LogFileNameNPath; + rfAppender.AppendToFile = true; + + // According to the listings on the blog site + // http://blog.aggregatedintelligence.com/2009/08/log4net-logging-levels-available.html + // Error, will log Error, Fatal + // Info, will log Info, Error, and Fatal + // Debug, will log Info, Error, Fatal and Debug + if (config.Detail == Logging_Detail.NONE) + rfAppender.Threshold = Level.Off; + else if (config.Detail == Logging_Detail.ERROR) + rfAppender.Threshold = Level.Error; + else if (config.Detail == Logging_Detail.INFO) + rfAppender.Threshold = Level.Info; + else if (config.Detail == Logging_Detail.DEBUG) + rfAppender.Threshold = Level.Debug; + + rfAppender.MaximumFileSize = String.Format("{0}MB", config.maxFileSizeInMB); + rfAppender.MaxSizeRollBackups = config.numOfBackupLogFiles; + + // Setting to RollingMode.Size will make MaxSizeRollBackups work + rfAppender.RollingStyle = RollingFileAppender.RollingMode.Size; + rfAppender.ActivateOptions(); + return rfAppender; + } + + #endregion + } +} diff --git a/File/Tail.cs b/File/Tail.cs new file mode 100644 index 0000000..bd20d8c --- /dev/null +++ b/File/Tail.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace Yaulw.File +{ + /// + /// A File SystemWatcher utility that functions like the infamous unix tail utility, + /// with a callback when the file get's created or updated. + /// + public class Tail + { + #region Public Events + + /// + /// Event Delegate for the Incoming Data Event + /// + /// object reference to the sending Tail Object + /// change data / or full data if bIsNewFile is true + /// true if a new file was created + public delegate void IncomingDataHandler(object sender, string newData, bool bIsNewFile); + + /// + /// Caller must subscribe to the Incoming Data Event + /// + public event IncomingDataHandler IncomingData = null; + + #endregion + + #region Private Members + + private string _FileNameNPath = ""; + private FileSystemWatcher _fileSystemWatcher = null; + private long _previousSeekPosition = 0; + private object _lockingObj = new Object(); + private const int MAX_BYTE_TO_READ_IN_ONE_TIME = 1024 * 16; + + #endregion + + #region Construction + + /// + /// Construct a new Tail Object to monitor the specified file + /// + /// File to Monitor with Tail + public Tail(string FileNameNPath) + { + _previousSeekPosition = 0; + _FileNameNPath = FileNameNPath; + } + + #endregion + + #region Public Methods + + /// + /// Start Monitoring the File + /// + public void StartMonitoring() + { + // Get File Information * Setup System Watcher * + FileInfo targetFile = new FileInfo(_FileNameNPath); + _fileSystemWatcher = new FileSystemWatcher(); + _fileSystemWatcher.IncludeSubdirectories = false; + _fileSystemWatcher.Path = targetFile.DirectoryName; + _fileSystemWatcher.Filter = targetFile.Name; + _fileSystemWatcher.Created += new FileSystemEventHandler(TargetFile_Created); + _fileSystemWatcher.Changed += new FileSystemEventHandler(TargetFile_Changed); + + // Perform an Initial read, if the file exists + if (targetFile.Exists) + TargetFile_Created(null, null); + + // Start up File System Watcher + _fileSystemWatcher.EnableRaisingEvents = true; + } + + /// + /// Stop Monitoring the File + /// + /// set to false, if you are running into trouble during a shutdown + public void StopMonitoring(bool bDispose = true) + { + _fileSystemWatcher.EnableRaisingEvents = false; + if(bDispose) + _fileSystemWatcher.Dispose(); + _fileSystemWatcher = null; + } + + /// + /// To read a file from Beginning to End. Call this function, + /// if you want to reset the position counter and start reading again from the beginning. + /// + /// the Contents of the File + public StringBuilder ReadFromBeginningToEndOfFile() + { + _previousSeekPosition = 0; + return ReadFileContentsTillEndStartingFromPreviousSeekPosition(); + } + + #endregion + + #region Private Helper Methods + + /// + /// Handled File System Watcher's Created Callback + /// + /// SystemWatcher object + /// SystemWatcher event args + private void TargetFile_Created(object source, FileSystemEventArgs e) + { + // Read the File Contents + StringBuilder sb = ReadFromBeginningToEndOfFile(); + + //call delegate with the string + if (IncomingData != null && sb.Length > 0) + IncomingData(this, sb.ToString(), true); + } + + /// + /// Handled File System Watcher's Changed Callback + /// + /// SystemWatcher object + /// SystemWatcher event args + private void TargetFile_Changed(object source, FileSystemEventArgs e) + { + // File size size changed to less! ~someone must have + // changed content inside of it + FileInfo targetFile = new FileInfo(_FileNameNPath); + bool bIsNew = false; + if (targetFile.Length < _previousSeekPosition) + { + _previousSeekPosition = 0; + bIsNew = true; + } + + // Read the File Contents + StringBuilder sb = ReadFileContentsTillEndStartingFromPreviousSeekPosition(); + + //call delegate with the string + if(IncomingData != null && sb.Length > 0) + IncomingData(this, sb.ToString(), bIsNew); + } + + /// + /// Helper Function to read a file from _previousSeekPosition to End. + /// + /// contents of file starting from _previousSeekPosition + private StringBuilder ReadFileContentsTillEndStartingFromPreviousSeekPosition() + { + lock (_lockingObj) + { + // Open the file + FileStream fs = new FileStream(_FileNameNPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + // Read File Contents into a StringBuilder + StringBuilder sb = new StringBuilder(); + bool bReadOrContinueReadingFile = true; + while (bReadOrContinueReadingFile) + { + // Position the file + _previousSeekPosition = (int)fs.Seek(_previousSeekPosition, SeekOrigin.Begin); + + //read from current seek position to end of file + byte[] bytesRead = new byte[MAX_BYTE_TO_READ_IN_ONE_TIME]; + int numBytes = fs.Read(bytesRead, 0, MAX_BYTE_TO_READ_IN_ONE_TIME); + _previousSeekPosition += numBytes; + + // Append Data to the String Builder + for (int i = 0; i < numBytes; i++) + sb.Append((char)bytesRead[i]); + + // If we have read up to MAX_BYTE_TO_READ_IN_ONE_TIME, then there could be more data... + bReadOrContinueReadingFile = (numBytes == MAX_BYTE_TO_READ_IN_ONE_TIME); + } + + // Close File + fs.Dispose(); + fs = null; + + // Return SB object + return sb; + } + } + + #endregion + } +} diff --git a/Installer/Common.cs b/Installer/Common.cs new file mode 100644 index 0000000..57684ed --- /dev/null +++ b/Installer/Common.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Yaulw.Process; +using System.IO; +using Yaulw.Tools; +using System.Diagnostics; +using Yaulw.Other; + +namespace Yaulw.Installer +{ + public static class Common + { + + #region Setup / Spawners + + /// + /// Spawn a Setup Process (Setup.exe for example) + /// + public static void PSetupSpwan(string SetupFileNameNPath, string param_s, bool bWait) + { + try + { + if (!String.IsNullOrEmpty(SetupFileNameNPath) && Tools.PathNaming.FileNameIsValid(System.IO.Path.GetFileName(SetupFileNameNPath))) + { + //string dir = Tools.PathNaming.MakeDirectoryPathValid(System.IO.Path.GetDirectoryName(SetupFileNameNPath)); + //SetupFileNameNPath = dir + System.IO.Path.DirectorySeparatorChar + System.IO.Path.GetFileName(SetupFileNameNPath); + + //if (!String.IsNullOrEmpty(param_s)) + //{ + // CMDline cmd = new CMDline(null, null); + // bool bIsValidParse = cmd.Parse(param_s.Split(' '), false); + // if (bIsValidParse) + // param_s = cmd.MakeValidCmdStrFromNewlyParsedCmdLine(); + // else + // return; // Invalid Parameters passed in that could cause OS Command Injection + //} + PStarter.StartProcess(PStartInfo.CreateProcess(SetupFileNameNPath, param_s, "", true, System.Diagnostics.ProcessWindowStyle.Hidden, false), bWait, false); + } + } + catch (Exception e) { throw e; } + } + + /// + /// Spawn a Setup Process (Setup.exe for example) using shell execute + /// + public static void PSetupSpwanShell(string SetupFileNameNPath, string param_s, bool bWait) + { + try + { + if (!String.IsNullOrEmpty(SetupFileNameNPath) && Tools.PathNaming.FileNameIsValid(System.IO.Path.GetFileName(SetupFileNameNPath))) + { + //string dir = Tools.PathNaming.MakeDirectoryPathValid(System.IO.Path.GetDirectoryName(SetupFileNameNPath)); + //SetupFileNameNPath = dir + System.IO.Path.DirectorySeparatorChar + System.IO.Path.GetFileName(SetupFileNameNPath); + + //if (!String.IsNullOrEmpty(param_s)) + //{ + // CMDline cmd = new CMDline(null, null); + // bool bIsValidParse = cmd.Parse(param_s.Split(' '), false); + // if (bIsValidParse) + // param_s = cmd.MakeValidCmdStrFromNewlyParsedCmdLine(); + // else + // return; // Invalid Parameters passed in that could cause OS Command Injection + //} + PStarter.StartProcess(PStartInfo.CreateProcess(SetupFileNameNPath, param_s, "", true, System.Diagnostics.ProcessWindowStyle.Hidden, true), bWait, false); + } + } + catch (Exception e) { throw e; } + } + + /// + /// Spawn a MSI Setup Process (*.msi) + /// + public static void PSetupMSIEXEC(string param_s) + { + try + { + string msiexec = System.Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\msiexec.exe"; + if (System.IO.File.Exists(msiexec)) + { + //if (!String.IsNullOrEmpty(param_s)) + //{ + // CMDline cmd = new CMDline(null, null); + // bool bIsValidParse = cmd.Parse(param_s.Split(' '), false); + // if (bIsValidParse) + // param_s = cmd.MakeValidCmdStrFromNewlyParsedCmdLine(); + // else + // return; // Invalid Parameters passed in that could cause OS Command Injection + //} + PStarter.StartProcess(PStartInfo.CreateProcess(msiexec, param_s, "", true, System.Diagnostics.ProcessWindowStyle.Hidden, false), true, false); + } + } + catch (Exception e) { throw e; } + } + + /// + /// Run a command on the commandline * Hidden * + /// + /// cmd to run + public static string RunCmdLine(string cmdline) + { + string result = PStarter.RunDosCommand(cmdline); + return result; + } + + #endregion + + #region Service (ServiceW Repeat with Check Existence) + + /// + /// Stop a service + /// + /// name of service to stop + /// true if successful, false otherwise + public static bool StopService(string ServiceName) + { + bool bSuccess = true; + if (ServiceW.DoesServiceExist(ServiceName)) + bSuccess = ServiceW.StopService(ServiceName, true, 120); + return bSuccess; + } + + /// + /// Stop a service + /// + /// name of service to stop + /// true if successful, false otherwise + public static bool StopService(string ServiceName, int nTimeoutBeforeKill) + { + bool bSuccess = true; + if (ServiceW.DoesServiceExist(ServiceName)) + bSuccess = ServiceW.StopService(ServiceName, true, nTimeoutBeforeKill); + return bSuccess; + } + + /// + /// start a service + /// + /// name of a service to start + /// true if successful, false otherwise + public static bool StartService(string ServiceName) + { + bool bSuccess = true; + if (ServiceW.DoesServiceExist(ServiceName)) + bSuccess = ServiceW.StartService(ServiceName, 120); + return bSuccess; + } + + /// + /// Does Service Exist + /// + /// Name of service to check + /// true if successful, false otherwise + public static bool ServiceExists(string ServiceName) + { + return ServiceW.DoesServiceExist(ServiceName); + } + + #endregion + + #region Windows Utils + + /// + /// Use this to get the complete path to a .net framework utility like gacutil.exe or + /// installutil.exe.. any .net framework utility. Will fall back to look in the %temp% folder, + /// if not found, giving the opportunity to deploy the util directly with the components + /// + /// the utility in .net you are looking for like gacutil.exe + /// the full filenameNpath or "", if no existing file found + public static string GetNetFrameworkUtilFileNameNPathFile(string UtilFileName) + { + string windir = System.Environment.GetEnvironmentVariable("windir", EnvironmentVariableTarget.Machine); + string NetFramework1_0 = windir + "\\Microsoft.Net\\Framework\\v1.0.3705"; + string NetFramework1_1 = windir + "\\Microsoft.Net\\Framework\\v1.1.4322"; + string NetFramework2_0 = windir + "\\Microsoft.Net\\Framework\\v2.0.50727"; + string NetFramework3_0 = windir + "\\Microsoft.Net\\Framework\\v3.0"; + string NetFramework3_5 = windir + "\\Microsoft.Net\\Framework\\v3.5"; + string NetFramework4_0 = windir + "\\Microsoft.Net\\Framework\\v4.0.30319"; + string TempPath = PathNaming.PathEndsWithNoSlash(Path.GetTempPath()); // We use this as a Fallback, in case file doesn't exist in the framework + string[] Frameworks = new string[] { NetFramework2_0, NetFramework4_0, NetFramework1_0, NetFramework1_1, NetFramework3_0, NetFramework3_5, TempPath }; + + string NetUtilFileNameNPath = ""; + foreach (string framework in Frameworks) + { + if (System.IO.File.Exists(framework + "\\" + UtilFileName)) + { + NetUtilFileNameNPath = framework + "\\" + UtilFileName; + return NetUtilFileNameNPath; + } + }; + return NetUtilFileNameNPath; + } + + /// + /// Quick Helper to get the Program Files Path for the specific system + /// + /// Program Files path with a '/' at the end + public static string GetProgramFilesPathOnSystemWithEndSlash() + { + string ProgramFiles = System.Environment.GetEnvironmentVariable("ProgramFiles(x86)"); + if (String.IsNullOrEmpty(ProgramFiles)) + ProgramFiles = System.Environment.GetEnvironmentVariable("ProgramFiles"); + return PathNaming.PathEndsWithSlash(ProgramFiles); + } + + /// + /// + /// + /// + public static string SetOwnership() + { + // in order to do any of this, sadly, we must first install the windows resource kit + //subinacl /subdirectories *.* /setowner=domainname\user + return String.Empty; + } + + /// + /// To grant the specified User or group Full Control permissions to the folder and its contents + /// + /// full path to folder/directory + /// domainname\administrator, any windows user or group + /// + public static bool GrantFullPermissionToFolderForUserOrGroup(string FolderNPath, string UserOrGroup) + { + if (Directory.Exists(FolderNPath)) + { + string command = String.Format("cacls \"{0}\" /t /e /g {1}:f", FolderNPath, UserOrGroup); + if (command.Contains("Invalid arguments.")) + return false; + else + return true; + } + return false; + } + + #endregion + + #region Windows Commands + + /// + /// Get The System up Time, important because we need SQL Server and Advantage up and running before + /// doing anything + /// + /// + public static TimeSpan GetSystemUpTime() + { + string Result = Yaulw.Installer.Common.RunCmdLine("net statistics server"); + int n = Result.IndexOf("Sessions accepted"); + if (n != -1) + { + Result = Result.Substring(0, n); + n = Result.IndexOf("Statistics since "); + if (n != -1) + { + Result = Result.Substring(n + "Statistics since ".Length); + Result = Result.Replace("\n", ""); + Result = Result.Replace("\r", ""); + + DateTime BootTime; + if (DateTime.TryParse(Result, out BootTime)) + return (DateTime.Now - BootTime); + } + } + + // Something went wrong with the first approach, + // let's try another... + using (var uptime = new PerformanceCounter("System", "System Up Time")) + { + uptime.NextValue(); //Call this an extra time before reading its value + return TimeSpan.FromSeconds(uptime.NextValue()); + } + } + + /// + /// Use this to create a quick File Shortcut on the system using ShellLink (File Only) + /// + /// A valid File on the system to point to (not a url) i believe url's are created differently + /// a valid .lnk file name and location where to put the shortcut + public static void CreateFileShortcut(string TargetFileNameNPath, string LnkFileNameNPath) + { + if (String.IsNullOrEmpty(TargetFileNameNPath) || String.IsNullOrEmpty(LnkFileNameNPath)) + return; + + if (!System.IO.File.Exists(TargetFileNameNPath)) + return; + + if (System.IO.File.Exists(LnkFileNameNPath)) + System.IO.File.Delete(LnkFileNameNPath); + + using (ShellLink shortcut = new ShellLink()) + { + shortcut.Target = TargetFileNameNPath; + shortcut.WorkingDirectory = Path.GetDirectoryName(TargetFileNameNPath); + shortcut.DisplayMode = ShellLink.LinkDisplayMode.edmNormal; + shortcut.Description = ""; // doesn't appear to do anything + shortcut.Save(LnkFileNameNPath); + } + } + + #endregion + + #region Resource Extraction + + /// + /// Extract a Binary Resource from a stream and write it to a file + /// + /// + /// + /// + /// + public static bool ExtractResourceStreamToFile(Stream resourceStream, string FileNameNPath, bool bForceWrite) + { + if (resourceStream != null && !String.IsNullOrEmpty(FileNameNPath)) + { + if (bForceWrite || !System.IO.File.Exists(FileNameNPath)) + { + + try + { + using (resourceStream) + using (BinaryReader br = new BinaryReader(resourceStream)) + using (BinaryWriter bw = new BinaryWriter(new FileStream(FileNameNPath, FileMode.Create))) + { + byte[] buffer = new byte[64 * 1024]; + int numread = br.Read(buffer, 0, buffer.Length); + while (numread > 0) + { + bw.Write(buffer, 0, numread); + numread = br.Read(buffer, 0, buffer.Length); + } + bw.Flush(); + } + return true; + } + catch (Exception) { /* ignore */ } + } + } + return false; + } + + #endregion + } +} diff --git a/Installer/FileIcon.cs b/Installer/FileIcon.cs new file mode 100644 index 0000000..a32bd2d --- /dev/null +++ b/Installer/FileIcon.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using System.Drawing; + +namespace Yaulw.Installer +{ + /// + /// Enables extraction of icons for any file type from + /// the Shell. + /// + /// + public class FileIcon + { + #region UnmanagedCode + private const int MAX_PATH = 260; + + [StructLayout(LayoutKind.Sequential)] + private struct SHFILEINFO + { + public IntPtr hIcon; + public int iIcon; + public int dwAttributes; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] + public string szDisplayName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] + public string szTypeName; + } + + [DllImport("shell32")] + private static extern int SHGetFileInfo( + string pszPath, + int dwFileAttributes, + ref SHFILEINFO psfi, + uint cbFileInfo, + uint uFlags); + + [DllImport("user32.dll")] + private static extern int DestroyIcon(IntPtr hIcon); + + private const int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x100; + private const int FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x2000; + private const int FORMAT_MESSAGE_FROM_HMODULE = 0x800; + private const int FORMAT_MESSAGE_FROM_STRING = 0x400; + private const int FORMAT_MESSAGE_FROM_SYSTEM = 0x1000; + private const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x200; + private const int FORMAT_MESSAGE_MAX_WIDTH_MASK = 0xFF; + [DllImport("kernel32")] + private extern static int FormatMessage( + int dwFlags, + IntPtr lpSource, + int dwMessageId, + int dwLanguageId, + string lpBuffer, + uint nSize, + int argumentsLong); + + [DllImport("kernel32")] + private extern static int GetLastError(); + #endregion + + #region Member Variables + private string fileName; + private string displayName; + private string typeName; + private SHGetFileInfoConstants flags; + private Icon fileIcon; + #endregion + + #region Enumerations + [Flags] + public enum SHGetFileInfoConstants : int + { + SHGFI_ICON = 0x100, // get icon + SHGFI_DISPLAYNAME = 0x200, // get display name + SHGFI_TYPENAME = 0x400, // get type name + SHGFI_ATTRIBUTES = 0x800, // get attributes + SHGFI_ICONLOCATION = 0x1000, // get icon location + SHGFI_EXETYPE = 0x2000, // return exe type + SHGFI_SYSICONINDEX = 0x4000, // get system icon index + SHGFI_LINKOVERLAY = 0x8000, // put a link overlay on icon + SHGFI_SELECTED = 0x10000, // show icon in selected state + SHGFI_ATTR_SPECIFIED = 0x20000, // get only specified attributes + SHGFI_LARGEICON = 0x0, // get large icon + SHGFI_SMALLICON = 0x1, // get small icon + SHGFI_OPENICON = 0x2, // get open icon + SHGFI_SHELLICONSIZE = 0x4, // get shell size icon + //SHGFI_PIDL = 0x8, // pszPath is a pidl + SHGFI_USEFILEATTRIBUTES = 0x10, // use passed dwFileAttribute + SHGFI_ADDOVERLAYS = 0x000000020, // apply the appropriate overlays + SHGFI_OVERLAYINDEX = 0x000000040 // Get the index of the overlay + } + #endregion + + #region Implementation + /// + /// Gets/sets the flags used to extract the icon + /// + public FileIcon.SHGetFileInfoConstants Flags + { + get + { + return flags; + } + set + { + flags = value; + } + } + + /// + /// Gets/sets the filename to get the icon for + /// + public string FileName + { + get + { + return fileName; + } + set + { + fileName = value; + } + } + + /// + /// Gets the icon for the chosen file + /// + public Icon ShellIcon + { + get + { + return fileIcon; + } + } + + /// + /// Gets the display name for the selected file + /// if the SHGFI_DISPLAYNAME flag was set. + /// + public string DisplayName + { + get + { + return displayName; + } + } + + /// + /// Gets the type name for the selected file + /// if the SHGFI_TYPENAME flag was set. + /// + public string TypeName + { + get + { + return typeName; + } + } + + /// + /// Gets the information for the specified + /// file name and flags. + /// + public void GetInfo() + { + fileIcon = null; + typeName = ""; + displayName = ""; + + SHFILEINFO shfi = new SHFILEINFO(); + uint shfiSize = (uint)Marshal.SizeOf(shfi.GetType()); + + int ret = SHGetFileInfo( + fileName, 0, ref shfi, shfiSize, (uint)(flags)); + if (ret != 0) + { + if (shfi.hIcon != IntPtr.Zero) + { + fileIcon = System.Drawing.Icon.FromHandle(shfi.hIcon); + // Now owned by the GDI+ object + //DestroyIcon(shfi.hIcon); + } + typeName = shfi.szTypeName; + displayName = shfi.szDisplayName; + } + else + { + + int err = GetLastError(); + Console.WriteLine("Error {0}", err); + string txtS = new string('\0', 256); + int len = FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + IntPtr.Zero, err, 0, txtS, 256, 0); + Console.WriteLine("Len {0} text {1}", len, txtS); + + // throw exception + + } + } + + /// + /// Constructs a new, default instance of the FileIcon + /// class. Specify the filename and call GetInfo() + /// to retrieve an icon. + /// + public FileIcon() + { + flags = SHGetFileInfoConstants.SHGFI_ICON | + SHGetFileInfoConstants.SHGFI_DISPLAYNAME | + SHGetFileInfoConstants.SHGFI_TYPENAME | + SHGetFileInfoConstants.SHGFI_ATTRIBUTES | + SHGetFileInfoConstants.SHGFI_EXETYPE; + } + /// + /// Constructs a new instance of the FileIcon class + /// and retrieves the icon, display name and type name + /// for the specified file. + /// + /// The filename to get the icon, + /// display name and type name for + public FileIcon(string fileName) + : this() + { + this.fileName = fileName; + GetInfo(); + } + /// + /// Constructs a new instance of the FileIcon class + /// and retrieves the information specified in the + /// flags. + /// + /// The filename to get information + /// for + /// The flags to use when extracting the + /// icon and other shell information. + public FileIcon(string fileName, FileIcon.SHGetFileInfoConstants flags) + { + this.fileName = fileName; + this.flags = flags; + GetInfo(); + } + + #endregion + } +} diff --git a/Installer/ShellLink.cs b/Installer/ShellLink.cs new file mode 100644 index 0000000..82c4b73 --- /dev/null +++ b/Installer/ShellLink.cs @@ -0,0 +1,953 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace Yaulw.Installer +{ + #region ShellLink Object + /// + /// Summary description for ShellLink. + /// + /// + public class ShellLink : IDisposable + { + #region ComInterop for IShellLink + + #region IPersist Interface + [ComImportAttribute()] + [GuidAttribute("0000010C-0000-0000-C000-000000000046")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IPersist + { + [PreserveSig] + //[helpstring("Returns the class identifier for the component object")] + void GetClassID(out Guid pClassID); + } + #endregion + + #region IPersistFile Interface + [ComImportAttribute()] + [GuidAttribute("0000010B-0000-0000-C000-000000000046")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IPersistFile + { + // can't get this to go if I extend IPersist, so put it here: + [PreserveSig] + void GetClassID(out Guid pClassID); + + //[helpstring("Checks for changes since last file write")] + void IsDirty(); + + //[helpstring("Opens the specified file and initializes the object from its contents")] + void Load( + [MarshalAs(UnmanagedType.LPWStr)] string pszFileName, + uint dwMode); + + //[helpstring("Saves the object into the specified file")] + void Save( + [MarshalAs(UnmanagedType.LPWStr)] string pszFileName, + [MarshalAs(UnmanagedType.Bool)] bool fRemember); + + //[helpstring("Notifies the object that save is completed")] + void SaveCompleted( + [MarshalAs(UnmanagedType.LPWStr)] string pszFileName); + + //[helpstring("Gets the current name of the file associated with the object")] + void GetCurFile( + [MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName); + } + #endregion + + #region IShellLink Interface + [ComImportAttribute()] + [GuidAttribute("000214EE-0000-0000-C000-000000000046")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IShellLinkA + { + //[helpstring("Retrieves the path and filename of a shell link object")] + void GetPath( + [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszFile, + int cchMaxPath, + ref _WIN32_FIND_DATAA pfd, + uint fFlags); + + //[helpstring("Retrieves the list of shell link item identifiers")] + void GetIDList(out IntPtr ppidl); + + //[helpstring("Sets the list of shell link item identifiers")] + void SetIDList(IntPtr pidl); + + //[helpstring("Retrieves the shell link description string")] + void GetDescription( + [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszFile, + int cchMaxName); + + //[helpstring("Sets the shell link description string")] + void SetDescription( + [MarshalAs(UnmanagedType.LPStr)] string pszName); + + //[helpstring("Retrieves the name of the shell link working directory")] + void GetWorkingDirectory( + [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszDir, + int cchMaxPath); + + //[helpstring("Sets the name of the shell link working directory")] + void SetWorkingDirectory( + [MarshalAs(UnmanagedType.LPStr)] string pszDir); + + //[helpstring("Retrieves the shell link command-line arguments")] + void GetArguments( + [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszArgs, + int cchMaxPath); + + //[helpstring("Sets the shell link command-line arguments")] + void SetArguments( + [MarshalAs(UnmanagedType.LPStr)] string pszArgs); + + //[propget, helpstring("Retrieves or sets the shell link hot key")] + void GetHotkey(out short pwHotkey); + //[propput, helpstring("Retrieves or sets the shell link hot key")] + void SetHotkey(short pwHotkey); + + //[propget, helpstring("Retrieves or sets the shell link show command")] + void GetShowCmd(out uint piShowCmd); + //[propput, helpstring("Retrieves or sets the shell link show command")] + void SetShowCmd(uint piShowCmd); + + //[helpstring("Retrieves the location (path and index) of the shell link icon")] + void GetIconLocation( + [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszIconPath, + int cchIconPath, + out int piIcon); + + //[helpstring("Sets the location (path and index) of the shell link icon")] + void SetIconLocation( + [MarshalAs(UnmanagedType.LPStr)] string pszIconPath, + int iIcon); + + //[helpstring("Sets the shell link relative path")] + void SetRelativePath( + [MarshalAs(UnmanagedType.LPStr)] string pszPathRel, + uint dwReserved); + + //[helpstring("Resolves a shell link. The system searches for the shell link object and updates the shell link path and its list of identifiers (if necessary)")] + void Resolve( + IntPtr hWnd, + uint fFlags); + + //[helpstring("Sets the shell link path and filename")] + void SetPath( + [MarshalAs(UnmanagedType.LPStr)] string pszFile); + } + + + [ComImportAttribute()] + [GuidAttribute("000214F9-0000-0000-C000-000000000046")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IShellLinkW + { + //[helpstring("Retrieves the path and filename of a shell link object")] + void GetPath( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, + int cchMaxPath, + ref _WIN32_FIND_DATAW pfd, + uint fFlags); + + //[helpstring("Retrieves the list of shell link item identifiers")] + void GetIDList(out IntPtr ppidl); + + //[helpstring("Sets the list of shell link item identifiers")] + void SetIDList(IntPtr pidl); + + //[helpstring("Retrieves the shell link description string")] + void GetDescription( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, + int cchMaxName); + + //[helpstring("Sets the shell link description string")] + void SetDescription( + [MarshalAs(UnmanagedType.LPWStr)] string pszName); + + //[helpstring("Retrieves the name of the shell link working directory")] + void GetWorkingDirectory( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, + int cchMaxPath); + + //[helpstring("Sets the name of the shell link working directory")] + void SetWorkingDirectory( + [MarshalAs(UnmanagedType.LPWStr)] string pszDir); + + //[helpstring("Retrieves the shell link command-line arguments")] + void GetArguments( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, + int cchMaxPath); + + //[helpstring("Sets the shell link command-line arguments")] + void SetArguments( + [MarshalAs(UnmanagedType.LPWStr)] string pszArgs); + + //[propget, helpstring("Retrieves or sets the shell link hot key")] + void GetHotkey(out short pwHotkey); + //[propput, helpstring("Retrieves or sets the shell link hot key")] + void SetHotkey(short pwHotkey); + + //[propget, helpstring("Retrieves or sets the shell link show command")] + void GetShowCmd(out uint piShowCmd); + //[propput, helpstring("Retrieves or sets the shell link show command")] + void SetShowCmd(uint piShowCmd); + + //[helpstring("Retrieves the location (path and index) of the shell link icon")] + void GetIconLocation( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, + int cchIconPath, + out int piIcon); + + //[helpstring("Sets the location (path and index) of the shell link icon")] + void SetIconLocation( + [MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, + int iIcon); + + //[helpstring("Sets the shell link relative path")] + void SetRelativePath( + [MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, + uint dwReserved); + + //[helpstring("Resolves a shell link. The system searches for the shell link object and updates the shell link path and its list of identifiers (if necessary)")] + void Resolve( + IntPtr hWnd, + uint fFlags); + + //[helpstring("Sets the shell link path and filename")] + void SetPath( + [MarshalAs(UnmanagedType.LPWStr)] string pszFile); + } + #endregion + + #region ShellLinkCoClass + [GuidAttribute("00021401-0000-0000-C000-000000000046")] + [ClassInterfaceAttribute(ClassInterfaceType.None)] + [ComImportAttribute()] + private class CShellLink{} + + #endregion + + #region Private IShellLink enumerations + private enum EShellLinkGP : uint + { + SLGP_SHORTPATH = 1, + SLGP_UNCPRIORITY = 2 + } + + [Flags] + private enum EShowWindowFlags : uint + { + SW_HIDE = 0, + SW_SHOWNORMAL = 1, + SW_NORMAL = 1, + SW_SHOWMINIMIZED = 2, + SW_SHOWMAXIMIZED = 3, + SW_MAXIMIZE = 3, + SW_SHOWNOACTIVATE = 4, + SW_SHOW = 5, + SW_MINIMIZE = 6, + SW_SHOWMINNOACTIVE = 7, + SW_SHOWNA = 8, + SW_RESTORE = 9, + SW_SHOWDEFAULT = 10, + SW_MAX = 10 + } + #endregion + + #region IShellLink Private structs + + [StructLayoutAttribute(LayoutKind.Sequential, Pack=4, Size=0, + CharSet=CharSet.Unicode)] + private struct _WIN32_FIND_DATAW + { + public uint dwFileAttributes; + public _FILETIME ftCreationTime; + public _FILETIME ftLastAccessTime; + public _FILETIME ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + public uint dwReserved0; + public uint dwReserved1; + [MarshalAs(UnmanagedType.ByValTStr , SizeConst = 260)] // MAX_PATH + public string cFileName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] + public string cAlternateFileName; + } + + [StructLayoutAttribute(LayoutKind.Sequential, Pack=4, Size=0, + CharSet=CharSet.Ansi)] + private struct _WIN32_FIND_DATAA + { + public uint dwFileAttributes; + public _FILETIME ftCreationTime; + public _FILETIME ftLastAccessTime; + public _FILETIME ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + public uint dwReserved0; + public uint dwReserved1; + [MarshalAs(UnmanagedType.ByValTStr , SizeConst = 260)] // MAX_PATH + public string cFileName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] + public string cAlternateFileName; + } + + [StructLayoutAttribute(LayoutKind.Sequential, Pack=4, Size=0)] + private struct _FILETIME + { + public uint dwLowDateTime; + public uint dwHighDateTime; + } + #endregion + + #region UnManaged Methods + private class UnManagedMethods + { + [DllImport("Shell32", CharSet=CharSet.Auto)] + internal extern static int ExtractIconEx ( + [MarshalAs(UnmanagedType.LPTStr)] + string lpszFile, + int nIconIndex, + IntPtr[] phIconLarge, + IntPtr[] phIconSmall, + int nIcons); + + [DllImport("user32")] + internal static extern int DestroyIcon(IntPtr hIcon); + } + #endregion + + #endregion + + #region Enumerations + /// + /// Flags determining how the links with missing + /// targets are resolved. + /// + [Flags] + public enum EShellLinkResolveFlags : uint + { + /// + /// Allow any match during resolution. Has no effect + /// on ME/2000 or above, use the other flags instead. + /// + SLR_ANY_MATCH = 0x2, + /// + /// Call the Microsoft Windows Installer. + /// + SLR_INVOKE_MSI = 0x80, + /// + /// Disable distributed link tracking. By default, + /// distributed link tracking tracks removable media + /// across multiple devices based on the volume name. + /// It also uses the UNC path to track remote file + /// systems whose drive letter has changed. Setting + /// SLR_NOLINKINFO disables both types of tracking. + /// + SLR_NOLINKINFO = 0x40, + /// + /// Do not display a dialog box if the link cannot be resolved. + /// When SLR_NO_UI is set, a time-out value that specifies the + /// maximum amount of time to be spent resolving the link can + /// be specified in milliseconds. The function returns if the + /// link cannot be resolved within the time-out duration. + /// If the timeout is not set, the time-out duration will be + /// set to the default value of 3,000 milliseconds (3 seconds). + /// + SLR_NO_UI = 0x1, + /// + /// Not documented in SDK. Assume same as SLR_NO_UI but + /// intended for applications without a hWnd. + /// + SLR_NO_UI_WITH_MSG_PUMP = 0x101, + /// + /// Do not update the link information. + /// + SLR_NOUPDATE = 0x8, + /// + /// Do not execute the search heuristics. + /// + SLR_NOSEARCH = 0x10, + /// + /// Do not use distributed link tracking. + /// + SLR_NOTRACK = 0x20, + /// + /// If the link object has changed, update its path and list + /// of identifiers. If SLR_UPDATE is set, you do not need to + /// call IPersistFile::IsDirty to determine whether or not + /// the link object has changed. + /// + SLR_UPDATE = 0x4 + } + + public enum LinkDisplayMode : uint + { + edmNormal = EShowWindowFlags.SW_NORMAL, + edmMinimized = EShowWindowFlags.SW_SHOWMINNOACTIVE, + edmMaximized = EShowWindowFlags.SW_MAXIMIZE + } + #endregion + + #region Member Variables + // Use Unicode (W) under NT, otherwise use ANSI + private IShellLinkW linkW; + private IShellLinkA linkA; + private string shortcutFile = ""; + #endregion + + #region Constructor + /// + /// Creates an instance of the Shell Link object. + /// + public ShellLink() + { + if (System.Environment.OSVersion.Platform == PlatformID.Win32NT) + { + linkW = (IShellLinkW)new CShellLink(); + } + else + { + linkA = (IShellLinkA)new CShellLink(); + } + } + + /// + /// Creates an instance of a Shell Link object + /// from the specified link file + /// + /// The Shortcut file to open + public ShellLink(string linkFile) : this() + { + Open(linkFile); + } + #endregion + + #region Destructor and Dispose + /// + /// Call dispose just in case it hasn't happened yet + /// + ~ShellLink() + { + Dispose(); + } + + /// + /// Dispose the object, releasing the COM ShellLink object + /// + public void Dispose() + { + if (linkW != null ) + { + Marshal.ReleaseComObject(linkW); + linkW = null; + } + if (linkA != null) + { + Marshal.ReleaseComObject(linkA); + linkA = null; + } + } + #endregion + + #region Implementation + public string ShortCutFile + { + get + { + return this.shortcutFile; + } + set + { + this.shortcutFile = value; + } + } + + /// + /// Gets a System.Drawing.Icon containing the icon for this + /// ShellLink object. + /// + public Icon LargeIcon + { + get + { + return getIcon(true); + } + } + + public Icon SmallIcon + { + get + { + return getIcon(false); + } + } + + private Icon getIcon(bool large) + { + // Get icon index and path: + int iconIndex = 0; + StringBuilder iconPath = new StringBuilder(260, 260); + if (linkA == null) + { + linkW.GetIconLocation(iconPath, iconPath.Capacity, out iconIndex); + } + else + { + linkA.GetIconLocation(iconPath, iconPath.Capacity, out iconIndex); + } + string iconFile = iconPath.ToString(); + + // If there are no details set for the icon, then we must use + // the shell to get the icon for the target: + if (iconFile.Length == 0) + { + // Use the FileIcon object to get the icon: + FileIcon.SHGetFileInfoConstants flags = + FileIcon.SHGetFileInfoConstants.SHGFI_ICON | + FileIcon.SHGetFileInfoConstants.SHGFI_ATTRIBUTES; + if (large) + { + flags = flags | FileIcon.SHGetFileInfoConstants.SHGFI_LARGEICON; + } + else + { + flags = flags | FileIcon.SHGetFileInfoConstants.SHGFI_SMALLICON; + } + FileIcon fileIcon = new FileIcon(Target, flags); + return fileIcon.ShellIcon; + } + else + { + // Use ExtractIconEx to get the icon: + IntPtr[] hIconEx = new IntPtr[1] {IntPtr.Zero}; + int iconCount = 0; + if (large) + { + iconCount = UnManagedMethods.ExtractIconEx( + iconFile, + iconIndex, + hIconEx, + null, + 1); + } + else + { + iconCount = UnManagedMethods.ExtractIconEx( + iconFile, + iconIndex, + null, + hIconEx, + 1); + } + // If success then return as a GDI+ object + Icon icon = null; + if (hIconEx[0] != IntPtr.Zero) + { + icon = Icon.FromHandle(hIconEx[0]); + //UnManagedMethods.DestroyIcon(hIconEx[0]); + } + return icon; + } + } + + /// + /// Gets the path to the file containing the icon for this shortcut. + /// + public string IconPath + { + get + { + StringBuilder iconPath = new StringBuilder(260, 260); + int iconIndex = 0; + if (linkA == null) + { + linkW.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); + } + else + { + linkA.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); + } + return iconPath.ToString(); + } + set + { + StringBuilder iconPath = new StringBuilder(260, 260); + int iconIndex = 0; + if (linkA == null) + { + linkW.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); + } + else + { + linkA.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); + } + if (linkA == null) + { + linkW.SetIconLocation(value, iconIndex); + } + else + { + linkA.SetIconLocation(value, iconIndex); + } + } + } + + /// + /// Gets the index of this icon within the icon path's resources + /// + public int IconIndex + { + get + { + StringBuilder iconPath = new StringBuilder(260, 260); + int iconIndex = 0; + if (linkA == null) + { + linkW.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); + } + else + { + linkA.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); + } + return iconIndex; + } + set + { + StringBuilder iconPath = new StringBuilder(260, 260); + int iconIndex = 0; + if (linkA == null) + { + linkW.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); + } + else + { + linkA.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); + } + if (linkA == null) + { + linkW.SetIconLocation(iconPath.ToString(), value); + } + else + { + linkA.SetIconLocation(iconPath.ToString(), value); + } + } + } + + /// + /// Gets/sets the fully qualified path to the link's target + /// + public string Target + { + get + { + StringBuilder target = new StringBuilder(260, 260); + if (linkA == null) + { + _WIN32_FIND_DATAW fd = new _WIN32_FIND_DATAW(); + linkW.GetPath(target, target.Capacity, ref fd, + (uint)EShellLinkGP.SLGP_UNCPRIORITY); + } + else + { + _WIN32_FIND_DATAA fd = new _WIN32_FIND_DATAA(); + linkA.GetPath(target, target.Capacity, ref fd, + (uint)EShellLinkGP.SLGP_UNCPRIORITY); + } + return target.ToString(); + } + set + { + if (linkA == null) + { + linkW.SetPath(value); + } + else + { + linkA.SetPath(value); + } + } + } + + /// + /// Gets/sets the Working Directory for the Link + /// + public string WorkingDirectory + { + get + { + StringBuilder path = new StringBuilder(260, 260); + if (linkA == null) + { + linkW.GetWorkingDirectory(path, path.Capacity); + } + else + { + linkA.GetWorkingDirectory(path, path.Capacity); + } + return path.ToString(); + } + set + { + if (linkA == null) + { + linkW.SetWorkingDirectory(value); + } + else + { + linkA.SetWorkingDirectory(value); + } + } + } + + /// + /// Gets/sets the description of the link + /// + public string Description + { + get + { + StringBuilder description = new StringBuilder(1024, 1024); + if (linkA == null) + { + linkW.GetDescription(description, description.Capacity); + } + else + { + linkA.GetDescription(description, description.Capacity); + } + return description.ToString(); + } + set + { + if (linkA == null) + { + linkW.SetDescription(value); + } + else + { + linkA.SetDescription(value); + } + } + } + + /// + /// Gets/sets any command line arguments associated with the link + /// + public string Arguments + { + get + { + StringBuilder arguments = new StringBuilder(260, 260); + if (linkA == null) + { + linkW.GetArguments(arguments, arguments.Capacity); + } + else + { + linkA.GetArguments(arguments, arguments.Capacity); + } + return arguments.ToString(); + } + set + { + if (linkA == null) + { + linkW.SetArguments(value); + } + else + { + linkA.SetArguments(value); + } + } + } + + /// + /// Gets/sets the initial display mode when the shortcut is + /// run + /// + public LinkDisplayMode DisplayMode + { + get + { + uint cmd = 0; + if (linkA == null) + { + linkW.GetShowCmd(out cmd); + } + else + { + linkA.GetShowCmd(out cmd); + } + return (LinkDisplayMode)cmd; + } + set + { + if (linkA == null) + { + linkW.SetShowCmd((uint)value); + } + else + { + linkA.SetShowCmd((uint)value); + } + } + } + + /// + /// Gets/sets the HotKey to start the shortcut (if any) + /// + public Keys HotKey + { + get + { + short key = 0; + if (linkA == null) + { + linkW.GetHotkey(out key); + } + else + { + linkA.GetHotkey(out key); + } + return (Keys)key; + } + set + { + if (linkA == null) + { + linkW.SetHotkey((short)value); + } + else + { + linkA.SetHotkey((short)value); + } + } + } + + /// + /// Saves the shortcut to ShortCutFile. + /// + public void Save() + { + Save(shortcutFile); + } + + /// + /// Saves the shortcut to the specified file + /// + /// The shortcut file (.lnk) + public void Save( + string linkFile + ) + { + // Save the object to disk + if (linkA == null) + { + ((IPersistFile)linkW).Save(linkFile, true); + shortcutFile = linkFile; + } + else + { + ((IPersistFile)linkA).Save(linkFile, true); + shortcutFile = linkFile; + } + } + + /// + /// Loads a shortcut from the specified file + /// + /// The shortcut file (.lnk) to load + public void Open( + string linkFile + ) + { + Open(linkFile, + IntPtr.Zero, + (EShellLinkResolveFlags.SLR_ANY_MATCH | + EShellLinkResolveFlags.SLR_NO_UI), + 1); + } + + /// + /// Loads a shortcut from the specified file, and allows flags controlling + /// the UI behaviour if the shortcut's target isn't found to be set. + /// + /// The shortcut file (.lnk) to load + /// The window handle of the application's UI, if any + /// Flags controlling resolution behaviour + public void Open( + string linkFile, + IntPtr hWnd, + EShellLinkResolveFlags resolveFlags + ) + { + Open(linkFile, + hWnd, + resolveFlags, + 1); + } + + /// + /// Loads a shortcut from the specified file, and allows flags controlling + /// the UI behaviour if the shortcut's target isn't found to be set. If + /// no SLR_NO_UI is specified, you can also specify a timeout. + /// + /// The shortcut file (.lnk) to load + /// The window handle of the application's UI, if any + /// Flags controlling resolution behaviour + /// Timeout if SLR_NO_UI is specified, in ms. + public void Open( + string linkFile, + IntPtr hWnd, + EShellLinkResolveFlags resolveFlags, + ushort timeOut + ) + { + uint flags; + + if ((resolveFlags & EShellLinkResolveFlags.SLR_NO_UI) + == EShellLinkResolveFlags.SLR_NO_UI) + { + flags = (uint)((int)resolveFlags | (timeOut << 16)); + } + else + { + flags = (uint)resolveFlags; + } + + if (linkA == null) + { + ((IPersistFile)linkW).Load(linkFile, 0); //STGM_DIRECT) + linkW.Resolve(hWnd, flags); + this.shortcutFile = linkFile; + } + else + { + ((IPersistFile)linkA).Load(linkFile, 0); //STGM_DIRECT) + linkA.Resolve(hWnd, flags); + this.shortcutFile = linkFile; + } + } + #endregion + } + #endregion +} diff --git a/Monitor/MonitorDataStore.cs b/Monitor/MonitorDataStore.cs new file mode 100644 index 0000000..a8c9e9b --- /dev/null +++ b/Monitor/MonitorDataStore.cs @@ -0,0 +1,1698 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; +using System.Collections; +using System.IO; +using Yaulw.Process; +using Yaulw.Other; +using Yaulw.Xml; + +namespace Yaulw.Monitor +{ + #region XML Data Definition + + /// + /// Serializable Xml Object used to store Monitor Configuration data + /// + [XmlRoot("configuration", Namespace = "Monitor", IsNullable = false)] + public class MonitorConfiguration : ICloneable + { + /// + /// MonitoredProcesses Member Tag <> holds list of ProcessExe's to Monitor + /// + public MonitoredProcessesW MonitoredProcesses = null; + + /// + /// MonitoredServices Member Tag <> holds list of Services to Monitor + /// + public MonitoredServicesW MonitoredServices = null; + + /// + /// Monitor Configuration - Constructor + /// + public MonitorConfiguration() + { + MonitoredProcesses = new MonitoredProcessesW(); + MonitoredServices = new MonitoredServicesW(); + } + + #region Internal Monitor Properties N' Methods + + /// + /// True if Either Any Processes or Any Services are Configured + /// + internal bool ConfiguredHasAny { get { return (MonitoredProcesses.ProcessExes.Length > 0 || MonitoredServices.ServiceExes.Length > 0); } } + + /// + /// True if Any Processes are Configured + /// + internal bool ConfiguredHasAnyProcesses { get { return (MonitoredProcesses.ProcessExes.Length > 0); } } + + /// + /// True if Any Services are Configured + /// + internal bool ConfiguredHasAnyServices { get { return (MonitoredServices.ServiceExes.Length > 0); } } + + /// + /// Clears all Start and Fail Times for Both Processes and Services + /// + internal void ClearAllStartNFailTimes() + { + MonitoredProcesses.ClearProcessExeStartNFailTimes(); + MonitoredServices.ClearServiceExeStartNFailTimes(); + } + + /// + /// Check to see if there are any Start or Fail Times, + /// if there aren't. Consider this configuration as newly loaded. + /// + /// true, if there are any Start or Fail Times recorded, false otherwise + internal bool HasAnyStartNFailTimes() + { + if (MonitoredProcesses.HasAnyProcessExeStartNFailTimes() || + MonitoredServices.HasAnyServiceExeStartNFailTimes()) + return true; + else + return false; + } + + #endregion + + /// + /// MonitoredProcessesW - (Wrapper) around ProcessExe ArrayList to work with XML + /// + public class MonitoredProcessesW : IComparable, ICloneable + { + #region Private Members + + private ArrayList m_ArrayList; + + #endregion + + #region Construction + + /// + /// Constructor for MonitoredProcessesW + /// + public MonitoredProcessesW() + { + m_ArrayList = new ArrayList(); + } + + #endregion + + #region XML Process Element + + /// + /// Property useful to directly Get/Set the ProcessExes as an Array + /// + [XmlElement("Process")] + public ProcessExe[] ProcessExes + { + get + { + ProcessExe[] processExes = new ProcessExe[m_ArrayList.Count]; + m_ArrayList.CopyTo(processExes); + return processExes; + } + set + { + if (value == null) return; + ProcessExe[] processExes = (ProcessExe[])value; + m_ArrayList.Clear(); + foreach (ProcessExe processExe in processExes) + AddProcessExe(processExe); + } + } + + #endregion + + #region Public Get Functions + + /// + /// Use this to get all processes settings for a specific Process Exe File + /// + /// specific process exe to get settings for + /// a list of process settings for the specified exe or empty list if not found + public ProcessExe[] GetProcessExesByProcessExeFileNameNPath(string ProcessExeFileNameNPath) + { + List foundProcessExes = new List(); + foreach (ProcessExe _cmpProcessExe in m_ArrayList) + { + if (String.Compare(_cmpProcessExe.ProcessExeFileNameNPath, ProcessExeFileNameNPath, true) == 0) + foundProcessExes.Add(_cmpProcessExe); + } + return foundProcessExes.ToArray(); + } + + /// + /// Use this to get all processes settings for a specific Process Exe FileName without Extension + /// + /// specific process exe fileName without Extension to get settings for + /// a list of process settings for the specified exe or empty list if not found + public ProcessExe[] GetProcessExesByProcessExeFileNameWithoutExtension(string ProcessExeFileNameWithoutExtension) + { + List foundProcessExes = new List(); + foreach (ProcessExe _cmpProcessExe in m_ArrayList) + { + if (String.Compare(Path.GetFileNameWithoutExtension(_cmpProcessExe.ProcessExeFileNameNPath), ProcessExeFileNameWithoutExtension, true) == 0) + foundProcessExes.Add(_cmpProcessExe); + } + return foundProcessExes.ToArray(); + } + + #endregion + + #region Public Add Functions + + /// + /// Helper function to Add a Process Exe + /// + /// ProcessExe to Add + /// true if successfull, false otherwise + public bool AddProcessExe(ProcessExe processExe) + { + bool bAddSuccess = false; + if (processExe != null && !String.IsNullOrEmpty(processExe.ProcessExeFileNameNPath)) + { + if (processExe.CommandLinePrms == null) + processExe.CommandLinePrms = String.Empty; + if (processExe.WorkingDirectory == null) + processExe.WorkingDirectory = String.Empty; + + ProcessExe pFound = null; + if (ProcessExeFileExistsLocally(processExe) && IsNotExcludedProcessExe(processExe) && IsUniqueProcessExe(processExe, out pFound)) + { + bAddSuccess = (m_ArrayList.Add(processExe) >= 0); + } + else if (pFound != null) + { + // if Working Directory isn't the same, we allow updating it via AddProcessExe Call * Perform Update * + bAddSuccess = (String.Compare(pFound.WorkingDirectory, processExe.WorkingDirectory, true) != 0); + if (bAddSuccess) + pFound.WorkingDirectory = processExe.WorkingDirectory; + } + } + return bAddSuccess; + } + + /// + /// Helper function to Add a Process Exe + /// + /// specify a fileName and path for a process + /// specify a command-line params to use for the process + /// specify the working directory to use for the process + /// true if successfull, false otherwise + //public bool AddProcessExe(string ProcessExeFileNameNPath, string CommandLinePrms, string WorkingDirectory = "") + public bool AddProcessExe(string ProcessExeFileNameNPath, string CommandLinePrms, string WorkingDirectory) + { + bool bAddSuccess = false; + if (!String.IsNullOrEmpty(ProcessExeFileNameNPath)) + { + if (CommandLinePrms == null) + CommandLinePrms = String.Empty; + if (WorkingDirectory == null) + WorkingDirectory = String.Empty; + + ProcessExe processToAdd = new ProcessExe(Yaulw.Win32.Functions.GetLongFileNameNPathOrPath(ProcessExeFileNameNPath.Trim()), CommandLinePrms.Trim(), WorkingDirectory.Trim()); + ProcessExe pFound = null; + if (ProcessExeFileExistsLocally(processToAdd) && IsNotExcludedProcessExe(processToAdd) && IsUniqueProcessExe(processToAdd, out pFound)) + { + bAddSuccess = (m_ArrayList.Add(processToAdd) >= 0); + } + else if (pFound != null) + { + // if Working Directory isn't the same, we allow updating it via AddProcessExe Call * Perform Update * + bAddSuccess = (String.Compare(pFound.WorkingDirectory, processToAdd.WorkingDirectory, true) != 0); + if (bAddSuccess) + pFound.WorkingDirectory = processToAdd.WorkingDirectory; + } + } + return bAddSuccess; + } + + /// + /// Helper function to check whether we can Add a Process Exe + /// + /// specify a fileName and path for a process + /// specify a command-line params to use for the process + /// specify the working directory to use for the process + /// true if successfull, false otherwise + //public bool CanAddProcessExe(string ProcessExeFileNameNPath, string CommandLinePrms, string WorkingDirectory = "") + public bool CanAddProcessExe(string ProcessExeFileNameNPath, string CommandLinePrms, string WorkingDirectory) + { + bool bCanAddSuccess = false; + if (!String.IsNullOrEmpty(ProcessExeFileNameNPath)) + { + if (CommandLinePrms == null) + CommandLinePrms = String.Empty; + if (WorkingDirectory == null) + WorkingDirectory = String.Empty; + + ProcessExe processToAdd = new ProcessExe(Yaulw.Win32.Functions.GetLongFileNameNPathOrPath(ProcessExeFileNameNPath.Trim()), CommandLinePrms.Trim(), WorkingDirectory.Trim()); + ProcessExe pFound = null; + if (ProcessExeFileExistsLocally(processToAdd) && IsNotExcludedProcessExe(processToAdd) && IsUniqueProcessExe(processToAdd, out pFound)) + { + bCanAddSuccess = true; + } + else if(pFound != null) + { + // if Working Directory isn't the same, we allow updating it via AddProcessExe Call + bCanAddSuccess = (String.Compare(pFound.WorkingDirectory, processToAdd.WorkingDirectory, true) != 0); + } + } + return bCanAddSuccess; + } + + #endregion + + #region Public Remove Functions + + /// + /// Helper function to Remove a Process Exe + /// + /// specify a fileName and path for a process + /// specify a command-line params to use for the process + /// specify the working directory to use for the process + /// true if successfull, false otherwise + public bool RemoveProcessExe(string ProcessExeFileNameNPath, string CommandLinePrms) + { + if (!String.IsNullOrEmpty(ProcessExeFileNameNPath)) + { + if (CommandLinePrms == null) + CommandLinePrms = String.Empty; + + int nIndex = -1; + for (int i = 0; i < m_ArrayList.Count; ++i) + { + ProcessExe _cmpProcessExe = (ProcessExe)m_ArrayList[i]; + if (_cmpProcessExe.FoundMatch(ProcessExeFileNameNPath.Trim(), CommandLinePrms.Trim())) + { + nIndex = i; + break; + } + } + + if (nIndex != -1) + { + m_ArrayList.RemoveAt(nIndex); + return true; + } + } + return false; + } + + #endregion + + #region Public Clear N' Query Functions + + /// + /// Clears all ProcessExes from the List + /// + public void Clear() { m_ArrayList.Clear(); } + + /// + /// Clears each Process's Start and Fail Time + /// + public void ClearProcessExeStartNFailTimes() { foreach (ProcessExe p in ProcessExes) p.ClearStartNFailTimes(); } + + /// + /// Check to see if there are any Start or Fail Times, + /// if there aren't. Consider this configuration as newly loaded. + /// + /// true, if there are any Start or Fail Times recorded, false otherwise + public bool HasAnyProcessExeStartNFailTimes() { foreach (ProcessExe p in ProcessExes) { if (p.LastFailedTimes.Count > 0) return true; } return false; } + + /// + /// Retrieve the Process Exe that matches the specified PID + /// + public ProcessExe ClearPidInformation() { foreach (ProcessExe p in ProcessExes) { p.PID = 0; } return null; } + + /// + /// Check to see if any of the Processes Configured has incomplete PID information, meaning that it never got started, + /// or failed to start, or was never mapped + /// + /// true, if any PID info is incomplete + public bool HasIncompletePidInformation() { foreach (ProcessExe p in ProcessExes) { if (p.PID <= 0) return true; } return false; } + + /// + /// Retrieve a list of all Pids that have been mapped. + /// Make sure to call HasIncompletePidInformation() prior calling this if you want to make sure all pid are returned. + /// * Function only returns valid pids * + /// + /// if set to true, includes Pids in an error state, false otherwise * should always use false * + /// an array of pids mapped + //public uint[] QueryAllPidInfo(bool bIncludeErrorStatePids = false) { List pids = new List(); foreach (ProcessExe p in ProcessExes) { if (p.PID > 0) { if (bIncludeErrorStatePids && p.InErrorState) { pids.Add(p.PID); } else if (!bIncludeErrorStatePids && !p.InErrorState) { pids.Add(p.PID); } } } return pids.ToArray(); } + public uint[] QueryAllPidInfo(bool bIncludeErrorStatePids, bool bIncludeNonRestartPids) + { + List pids = new List(); + foreach (ProcessExe p in ProcessExes) + { + if (p.PID > 0) + { + // bError = true && bInclude = true (include all) + // bError = false && bInclude = true (include all that don't have an error) + // bError = true && bInclude = false (include all that aren't restart) + // bError = false && bInclude = false (include none that have either) + if (bIncludeErrorStatePids && bIncludeNonRestartPids) + { + pids.Add(p.PID); + } + else if (!bIncludeErrorStatePids && bIncludeNonRestartPids) + { + if (!p.InErrorState) + pids.Add(p.PID); + } + else if (bIncludeErrorStatePids && !bIncludeNonRestartPids) + { + if (p.AllowRestart) + pids.Add(p.PID); + } + else if (!bIncludeErrorStatePids && !bIncludeNonRestartPids) + { + if (!p.InErrorState && p.AllowRestart) + pids.Add(p.PID); + } + } + } + return pids.ToArray(); + } + + /// + /// Retrieve the number of Processes now considered in an error state. + /// + /// Number of Pids in an error state + public uint NumberOfPidsInErrorState() { uint nErrorCount = 0; foreach (ProcessExe p in ProcessExes) { if (p.InErrorState) nErrorCount++; } return nErrorCount; } + + /// + /// Retrieve the Process Exe that matches the specified PID + /// + /// PID to look four + /// ProcessExe that matches the PID, null otherwise (if not found) + public ProcessExe GetProcessExeForPid(uint PID) { foreach (ProcessExe p in ProcessExes) { if (p.PID == PID) return p; } return null; } + + #endregion + + #region Private Helper Functions + + /// + /// Enforce Uniqueness, when adding Process Exe's to the DS + /// + /// a ProcessExe to check + /// the found ProcessExe that matches the check + /// true if unique, false otherwise + private bool IsUniqueProcessExe(ProcessExe processExe, out ProcessExe foundP) + { + foundP = null; + foreach (ProcessExe _cmpProcessExe in m_ArrayList) + { + // If ProcessExe FileName and Path, as well as CommandLine Prms Match, then it is NOT unique + if (_cmpProcessExe.FoundMatch(processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)) + { + foundP = _cmpProcessExe; + return false; + } + } + return true; + } + + /// + /// Enforce that the called didn't specify to exclude this Process + /// + /// a ProcessExe to check + /// true if not exclusive, false otherwise + private bool IsNotExcludedProcessExe(ProcessExe processExe) + { + // * Allow caller via the Statically SPecified Exclusion list to filter out processes + // that are considered invalid * + if (MonitorDataStore.MonitoredProcessNamesExclusionList != null && + MonitorDataStore.MonitoredProcessNamesExclusionList.Count > 0) + { + foreach (string ProcessName in MonitorDataStore.MonitoredProcessNamesExclusionList) + { + if (String.Compare(ProcessName, Path.GetFileNameWithoutExtension(processExe.ProcessExeFileNameNPath), true) == 0) + return false; + } + } + return true; + } + + /// + /// Enforce that the Process/Exe File must exist on the system, prior being added + /// + /// true if exists, false otherwise + private bool ProcessExeFileExistsLocally(ProcessExe processExe) + { + return System.IO.File.Exists(processExe.ProcessExeFileNameNPath); + } + + #endregion + + #region IComparable Members + + /// + /// Compares tow MonitoredProcessesW Objects with each other + /// + /// Pass in a valid MonitoredProcessesW Object + /// 0 if equal, -1 if obj is smaller, 1 if obj is bigger + public int CompareTo(object obj) + { + if (obj is MonitoredProcessesW) + { + MonitoredProcessesW w = (MonitoredProcessesW)obj; + + // First, do a simple count comparison + int wListCount = w.m_ArrayList.Count; + int thisListCount = this.m_ArrayList.Count; + if (wListCount == 0 && thisListCount != 0) + return -1; + else if (wListCount == 0 && thisListCount == 0) + return 0; + else if (wListCount != 0 && thisListCount == 0) + return 1; + else if (wListCount < thisListCount) + return -1; + else if (wListCount > thisListCount) + return 1; + + // The count must be the same * Hence, now we must actually + // compare each member to make sure that the lists are the same * + // ~Before we do this we should sort both lists, so that we can correctly compare + ArrayList List1 = (ArrayList)w.m_ArrayList.Clone(); + ArrayList List2 = (ArrayList)this.m_ArrayList.Clone(); + List1.Sort(); + List2.Sort(); + + // ~Iterate this Object's array list and compare each member + int nCompare = 0; + for (int i = 0; i < thisListCount; ++i) + { + ProcessExe p1 = (ProcessExe)List1[i]; + ProcessExe p2 = (ProcessExe)List2[i]; + nCompare = p1.CompareTo(p2); + if (nCompare != 0) + break; + } + + // If they are both still exactly equal, then let's + // now also compare the order + if (nCompare == 0) + { + for (int i = 0; i < w.m_ArrayList.Count; ++i) + { + ProcessExe p1 = (ProcessExe) w.m_ArrayList[i]; + ProcessExe p2 = (ProcessExe) this.m_ArrayList[i]; + nCompare = p1.CompareTo(p2); + if (nCompare != 0) + break; + } + } + + return nCompare; + } + else + { + throw new ArgumentException("object is not a MonitoredProcessesW"); + } + } + + #endregion + + #region ICloneable Members + + /// + /// Performs a Shallow Copy of MonitoredProcessesW + /// + /// a new MonitoredProcessesW Object + public object Clone() + { + MonitoredProcessesW w = new MonitoredProcessesW(); + w.m_ArrayList = (ArrayList)this.m_ArrayList.Clone(); + return w; + } + + #endregion + } + + /// + /// Process Exe (Image) Class that contains all settings to monitor + /// a process / executable image + /// + public class ProcessExe : IComparable, ICloneable + { + #region Public Properties + + /// + /// specify a fileName and path for a process + /// + [XmlText] + public string ProcessExeFileNameNPath { get { return _ProcessExeFileNameNPath; } set { if (!String.IsNullOrEmpty(value)) _ProcessExeFileNameNPath = value; } } + private string _ProcessExeFileNameNPath = ""; + + /// + /// specify a command-line params to use for the process + /// + [XmlAttribute("CommandLinePrms")] + public string CommandLinePrms { get { if (String.IsNullOrEmpty(_CommandLinePrms)) return ""; else return _CommandLinePrms; } set { if (!String.IsNullOrEmpty(value)) _CommandLinePrms = value; } } + private string _CommandLinePrms = ""; + + + /// + /// specify the working directory to use for the process + /// + [XmlAttribute("WorkingDirectory")] + public string WorkingDirectory { get { if(String.IsNullOrEmpty(_WorkingDirectory)) return ""; else return _WorkingDirectory; } set { if (!String.IsNullOrEmpty(value)) _WorkingDirectory = value; } } + private string _WorkingDirectory; + + /// + /// specify if process is allowed to be restarted + /// + [XmlAttribute("AllowRestart")] + public bool AllowRestart { get { return _AllowRestart; } set { _AllowRestart = value; } } + private bool _AllowRestart = true; + + #endregion + + #region Construction + + /// + /// Default Constructor * Don't use this * used only by serialization + /// + public ProcessExe() + { + this.PID = 0; + this.InErrorState = false; + } + + /// + /// Main Constructor * Use this * + /// + /// specify a fileName and path for a process + /// specify a command-line params to use for the process + /// specify the working directory to use for the process + public ProcessExe(string ProcessExeFileNameNPath, string CommandLinePrms, string WorkingDirectory) + { + if(!String.IsNullOrEmpty(ProcessExeFileNameNPath)) + this.ProcessExeFileNameNPath = ProcessExeFileNameNPath.Trim(); + if(!String.IsNullOrEmpty(CommandLinePrms)) + this.CommandLinePrms = CommandLinePrms.Trim(); + if(!String.IsNullOrEmpty(WorkingDirectory)) + this.WorkingDirectory = WorkingDirectory.Trim(); + this.PID = 0; + this.InErrorState = false; + } + + #endregion + + #region Internal Helper Functions N Properties + + /// + /// The name of the Process + /// + internal string ProcessName + { + get + { + if (!String.IsNullOrEmpty(this.ProcessExeFileNameNPath)) + { + string ProcessName = Path.GetFileNameWithoutExtension(this.ProcessExeFileNameNPath); + return ProcessName.ToLower(); + } + return String.Empty; + } + } + + /// + /// Clears all Start N' Fail Times for a process + /// + internal void ClearStartNFailTimes() + { + LastStartedTimes.Clear(); + LastStartedTimes.Clear(); + } + + /// + /// Keep Track if this Process is in an Error State + /// + internal bool InErrorState; + + /// + /// Internal Variable to Keep track of PID + /// + internal uint PID; + + /// + /// Keep track of all Last Failed DT Stamps + /// + internal readonly List LastFailedTimes = new List(); + + /// + /// Keep track of all Last Started DT Stamps + /// + internal readonly List LastStartedTimes = new List(); + + /// + /// Returns the Last Failed Stamp + /// + internal DateTime LastFailTime + { + get + { + if (LastFailedTimes.Count > 0) + return LastFailedTimes.Last(); + else + return DateTime.MinValue; + } + } + + /// + /// Allow specific amount of time to have passed since Last-Fail + /// + /// timespan that has to have occured since Last-Fail Time + /// true if the Last-Fail Exceeded the specified amount of time, false otherwise + internal bool LastFailTimeTimeoutExceeded(TimeSpan ts) + { + if (LastFailedTimes.Count > 0) + { + TimeSpan diff = DateTime.Now - LastFailTime; + if (diff < ts) + return false; + } + return true; + } + + /// + /// Returns the Last Started DT Stamp + /// + internal DateTime LastStartTime + { + get + { + if (LastStartedTimes.Count > 0) + return LastStartedTimes.Last(); + else + return DateTime.MinValue; + } + } + + /// + /// Allow specific amount of time to have passed since Last-Start + /// + /// timespan that has to have occured since Last-Start Time + /// true if the Last-Start Exceeded the specified amount of time, false otherwise + internal bool LastStartTimeTimeoutExceeded(TimeSpan ts) + { + if (LastStartedTimes.Count > 0) + { + TimeSpan diff = DateTime.Now - LastStartTime; + if (diff < ts) + return false; + } + return true; + } + + /// + /// Add a FailTime to keep track of for the given process + /// + /// Fail-Time + /// max capacity to store + //internal void AddFailTime(DateTime dtFail, int nMaxCapacity = 1) + internal void AddFailTime(DateTime dtFail, int nMaxCapacity) + { + if (dtFail != DateTime.MinValue) + { + // Make sure to always at least store one + if (nMaxCapacity < 1) + nMaxCapacity = 1; + + // Add and adjust according to capacity + LastFailedTimes.Add(dtFail); + if (LastFailedTimes.Count > nMaxCapacity) + { + int nRemoveCount = nMaxCapacity - LastFailedTimes.Count; + for (int i = 0; i < nRemoveCount; ++i) + LastFailedTimes.RemoveAt(i); + } + } + } + + /// + /// Add a StartTime to keep track of for the given process + /// + /// Start-Time + /// max capacity to store + //internal void AddStartTime(DateTime dtStart, int nMaxCapacity = 1) + internal void AddStartTime(DateTime dtStart, int nMaxCapacity) + { + if (dtStart != DateTime.MinValue) + { + // Make sure to always at least store one + if (nMaxCapacity < 1) + nMaxCapacity = 1; + + // Add and adjust according to capacity + LastStartedTimes.Add(dtStart); + if (LastStartedTimes.Count > nMaxCapacity) + { + int nRemoveCount = nMaxCapacity - LastStartedTimes.Count; + for (int i = 0; i < nRemoveCount; ++i) + LastStartedTimes.RemoveAt(i); + } + } + } + + /// + /// Retrieves the Number of times the process failed in the given timespan + /// + /// TimeSpan to subtract from DateTime.Now + /// number of times this Process Failed in the given timespan + internal int GetFailTimesInGivenTimeSpan(TimeSpan span) + { + if (LastFailedTimes.Count == 0) + return 0; + + int nCount = 0; + DateTime dtMin = DateTime.Now - span; + foreach (DateTime dt in LastFailedTimes) + { + if (dt >= dtMin) + ++nCount; + } + return nCount; + } + + /// + /// Checks to see if the Number of times the process failed in the given timespan + /// exceeds nMaxTry + /// + /// number of max times it is allowed to fail + /// TimeSpan to subtract from DateTime.Now + /// true if number of times in the given timespan exceeds nMaxTry, false otherwise + internal bool GetFailTimesInGivenTimeSpanMaxTryExceeded(TimeSpan span, uint nMaxTry) + { + if (LastFailedTimes.Count == 0) + return false; + + int nCount = GetFailTimesInGivenTimeSpan(span); + return (nCount > nMaxTry); + } + + /// + /// Retrieves the Number of times the process Started in the given timespan + /// + /// TimeSpan to subtract from DateTime.Now + /// number of times this Process Started in the given timespan + internal int GetStartTimesInGivenTimeSpan(TimeSpan span) + { + if (LastStartedTimes.Count == 0) + return 0; + + int nCount = 0; + DateTime dtMin = DateTime.Now - span; + foreach (DateTime dt in LastStartedTimes) + { + if (dt >= dtMin) + ++nCount; + } + return nCount; + } + + /// + /// Checks to see if the Number of times the process started in the given timespan + /// exceeds nMaxTry + /// + /// number of max times it is allowed to have started + /// TimeSpan to subtract from DateTime.Now + /// true if number of times in the given timespan exceeds nMaxTry, false otherwise + internal bool GetStartTimesInGivenTimeSpanMaxTryExceeded(TimeSpan span, uint nMaxTry) + { + if (LastStartedTimes.Count == 0) + return false; + + int nCount = GetStartTimesInGivenTimeSpan(span); + return (nCount > nMaxTry); + } + + /// + /// Returns true if the passed in ProcessExeFileNameNPath, CommandLinePrms + /// matches this object's members * NOT THE SAME AS IComparable * + /// IComparable does a Full Compare, whereas this only Compares ProcessExeFileNameNPath and CommandLinePrms + /// ~IComparable also includes WorkingDirectory + /// + internal bool FoundMatch(string ProcessExeFileNameNPath, string CommandLinePrms) + { + if ((String.Compare(this.ProcessExeFileNameNPath.Trim(), ProcessExeFileNameNPath.Trim(), true) == 0) && + (String.Compare(this.CommandLinePrms.Trim(), CommandLinePrms.Trim(), true) == 0)) + { + return true; + } + return false; + } + + #endregion + + #region IComparable Members + + /// + /// Compare the ProcessExe + /// + /// a valid ProcessExe Obj + /// 0 if equal, -1 if obj is smaller, +1 if obj is bigger + public int CompareTo(object obj) + { + if (obj is ProcessExe) + { + ProcessExe p = (ProcessExe)obj; + int nCompare = String.Compare(p.ProcessExeFileNameNPath.Trim(), this.ProcessExeFileNameNPath.Trim(), true); + if (nCompare == 0) + nCompare = String.Compare(p.CommandLinePrms.Trim(), this.CommandLinePrms.Trim(), true); + if (nCompare == 0) + nCompare = String.Compare(p.WorkingDirectory.Trim(), this.WorkingDirectory.Trim(), true); + if (nCompare == 0) + nCompare = this.AllowRestart.CompareTo(p.AllowRestart); + return nCompare; + } + else + { + throw new ArgumentException("object is not a ProcessExe"); + } + } + + #endregion + + #region ICloneable Members + + /// + /// Clones the ProcessExe Object + /// + /// a new ProcessExe Object + public object Clone() + { + ProcessExe process = new ProcessExe(this.ProcessExeFileNameNPath, this.CommandLinePrms, this.WorkingDirectory); + process.AllowRestart = this.AllowRestart; + return process; + } + + #endregion + } + + /// + /// MonitoredServicesW - (Wrapper) around ServiceExe ArrayList to work with XML + /// + public class MonitoredServicesW : IComparable, ICloneable + { + #region Private Members + + private ArrayList m_ArrayList; + + #endregion + + #region Construction + + /// + /// Constructor for MonitoredServicesW + /// + public MonitoredServicesW() + { + m_ArrayList = new ArrayList(); + } + + #endregion + + #region XML Service Element + + /// + /// Property useful to directly Get/Set the ServiceExes as an Array + /// + [XmlElement("Service")] + public ServiceExe[] ServiceExes + { + get + { + ServiceExe[] serviceExes = new ServiceExe[m_ArrayList.Count]; + m_ArrayList.CopyTo(serviceExes); + return serviceExes; + } + set + { + if (value == null) return; + ServiceExe[] serviceExes = (ServiceExe[])value; + m_ArrayList.Clear(); + foreach (ServiceExe serviceExe in serviceExes) + AddServiceExe(serviceExe); + } + } + + #endregion + + #region Public Get Function + + /// + /// Use this to get the ServiceExe Object for the Specified Service Name + /// + /// Name of Service To look for + /// the Service Exe Object or null, if not found + public ServiceExe GetServiceExeByServiceName(string Name) + { + if (String.IsNullOrEmpty(Name)) + { + foreach (ServiceExe _cmpServiceExe in m_ArrayList) + { + if (String.Compare(_cmpServiceExe.Name, Name, true) == 0) + return _cmpServiceExe; + } + } + return null; + } + + #endregion + + #region Public Add Functions + + /// + /// Helper function to Add a Service Exe + /// + /// a valid ServiceExe Object + /// true if successfull, false otherwise + public bool AddServiceExe(ServiceExe serviceExe) + { + bool bAddSuccess = false; + if (serviceExe != null && !String.IsNullOrEmpty(serviceExe.Name)) + { + if (ServiceExistsLocally(serviceExe) && IsUniqueServiceExe(serviceExe)) + bAddSuccess = (m_ArrayList.Add(serviceExe) >= 0); + } + return bAddSuccess; + } + + /// + /// Helper function to Add a Service Exe + /// + /// Name of Service + /// true if successfull, false otherwise + public bool AddServiceExe(string Name) + { + bool bAddSuccess = false; + if (!String.IsNullOrEmpty(Name)) + { + ServiceExe serviceToAdd = new ServiceExe(Name.Trim()); + if (ServiceExistsLocally(serviceToAdd) && IsUniqueServiceExe(serviceToAdd)) + bAddSuccess = (m_ArrayList.Add(serviceToAdd) >= 0); + } + return bAddSuccess; + } + + #endregion + + #region Public Remove Function + + /// + /// Helper function to Remove a Service Exe + /// + /// Name of Service + /// true if successfull, false otherwise + public bool RemoveServiceExe(string Name) + { + if (!String.IsNullOrEmpty(Name)) + { + int nIndex = -1; + for (int i = 0; i < m_ArrayList.Count; ++i) + { + ServiceExe _cmpServiceExe = (ServiceExe)m_ArrayList[i]; + if (_cmpServiceExe.FoundMatch(Name)) + { + nIndex = i; + break; + } + } + + if (nIndex != -1) + { + m_ArrayList.RemoveAt(nIndex); + return true; + } + } + return false; + } + + #endregion + + #region Public Clear N' Query Functions + + /// + /// Clears all ServiceExes from the List + /// + public void Clear() { m_ArrayList.Clear(); } + + /// + /// Clears each Services's Start and Fail Time + /// + public void ClearServiceExeStartNFailTimes() { foreach (ServiceExe s in ServiceExes) s.ClearStartNFailTimes(); } + + /// + /// Check to see if there are any Start or Fail Times, + /// if there aren't. Consider this configuration as newly loaded. + /// + /// true, if there are any Start or Fail Times recorded, false otherwise + public bool HasAnyServiceExeStartNFailTimes() { foreach (ServiceExe s in ServiceExes) { if (s.LastFailedTimes.Count > 0) return true; } return false; } + + /// + /// Retrieve a list of all Services that have been mapped + /// + /// if set to true, includes Services in an error state, false otherwise * should always use false * + /// if set true, includes Services that are marked as NonRestart + /// + public string[] QueryAllServiceInfo(bool bIncludeErrorStateServices, bool bIncludeNonRestartServices) + { + List services = new List(); + foreach (ServiceExe s in ServiceExes) + { + if (!String.IsNullOrEmpty(s.Name)) + { + // bError = true && bInclude = true (include all) + // bError = false && bInclude = true (include all that don't have an error) + // bError = true && bInclude = false (include all that aren't restart) + // bError = false && bInclude = false (include none that have either) + if (bIncludeErrorStateServices && bIncludeNonRestartServices) + { + services.Add(s.Name); + } + else if (!bIncludeErrorStateServices && bIncludeNonRestartServices) + { + if (!s.InErrorState) + services.Add(s.Name); + } + else if (bIncludeErrorStateServices && !bIncludeNonRestartServices) + { + if (s.AllowRestart) + services.Add(s.Name); + } + else if (!bIncludeErrorStateServices && !bIncludeNonRestartServices) + { + if (!s.InErrorState && s.AllowRestart) + services.Add(s.Name); + } + } + } + return services.ToArray(); + } + #endregion + + #region Private Helper Functions + + /// + /// Enforce Uniqueness, when adding Service Exe's to the DS + /// + /// a valid ServiceExe Object + /// true if unique, false otherwise + private bool IsUniqueServiceExe(ServiceExe serviceExe) + { + foreach (ServiceExe _cmpServiceExe in m_ArrayList) + { + // If ServiceExe Name Matches, then it is NOT unique + if (_cmpServiceExe.FoundMatch(serviceExe.Name)) + return false; + } + return true; + } + + /// + /// Enforce that the Service must exist on the system, prior being added + /// + /// a valid ServiceExe Object + /// true if exists, false otherwise + private bool ServiceExistsLocally(ServiceExe serviceExe) + { + bool bExists = ServiceW.DoesServiceExist(serviceExe.Name); + return bExists; + } + + #endregion + + #region IComparable Members + + /// + /// Compares two MonitoredServicesW Objects + /// + /// a valid MonitoredServicesW object + /// 0 if equal, -1 if obj is less, +1 if obj is more + public int CompareTo(object obj) + { + if (obj is MonitoredServicesW) + { + MonitoredServicesW sw = (MonitoredServicesW)obj; + + // First, do a simple count comparison + int wListCount = sw.m_ArrayList.Count; + int thisListCount = this.m_ArrayList.Count; + + if (wListCount == 0 && thisListCount != 0) + return -1; + else if (wListCount == 0 && thisListCount == 0) + return 0; + else if (wListCount != 0 && thisListCount == 0) + return 1; + else if (wListCount < thisListCount) + return -1; + else if (wListCount > thisListCount) + return 1; + + // The count must be the same * Hence, now we must actually + // compare each member to make sure that the lists are the same * + // ~but before we do that, we must sort the List first + ArrayList List1 = (ArrayList) sw.m_ArrayList.Clone(); + ArrayList List2 = (ArrayList) this.m_ArrayList.Clone(); + List1.Sort(); + List2.Sort(); + + // ~Iterate this Object's array list and compare each member + int nCompare = 0; + for (int i = 0; i < thisListCount; ++i) + { + ServiceExe s1 = (ServiceExe)List1[i]; + ServiceExe s2 = (ServiceExe)List2[i]; + nCompare = s1.CompareTo(s2); + if (nCompare != 0) + break; + } + + // If they are both still exactly equal, then let's + // now also compare the order + if (nCompare == 0) + { + for (int i = 0; i < sw.m_ArrayList.Count; ++i) + { + ServiceExe s1 = (ServiceExe)sw.m_ArrayList[i]; + ServiceExe s2 = (ServiceExe)this.m_ArrayList[i]; + nCompare = s1.CompareTo(s2); + if (nCompare != 0) + break; + } + } + return nCompare; + } + else + { + throw new ArgumentException("object is not a MonitoredServicesW"); + } + } + + #endregion + + #region ICloneable Members + + /// + /// Creates a shallow copy of MonitoredServicesW Obj + /// + /// a new MonitoredServicesW Obj + public object Clone() + { + MonitoredServicesW sw = new MonitoredServicesW(); + sw.m_ArrayList = (ArrayList)this.m_ArrayList.Clone(); + return sw; + } + + #endregion + } + + /// + /// Service Name/Path Class that contains all settings to monitor a service + /// + public class ServiceExe : IComparable, ICloneable + { + #region Public Properties + + /// + /// specify a Service Name + /// + [XmlText] + public string Name { get { return _Name; } set { if(!String.IsNullOrEmpty(value)) _Name = value; } } + private string _Name; + + /// + /// specify if service is allowed to be restarted + /// + [XmlAttribute("AllowRestart")] + public bool AllowRestart { get { return _AllowRestart; } set { _AllowRestart = value; } } + private bool _AllowRestart = true; + + #endregion + + #region Construction + + /// + /// Default Constructor * Don't use this * used only by serialization + /// + public ServiceExe() + { + this.InErrorState = false; + } + + /// + /// Main Constructor * Use this * + /// + /// specify the service Name + public ServiceExe(string Name) + { + if (!String.IsNullOrEmpty(Name)) + this.Name = Name.Trim(); + this.InErrorState = false; + } + + #endregion + + #region Internal Helper Functions + + /// + /// Returns true if the passed in Name matches this object's members *same as IComparable, + /// but func doesn't need creation of an object* + /// + /// specify the service Name + /// true if matched, false otherwise + internal bool FoundMatch(string Name) + { + if (Name != null) + { + if (String.Compare(this.Name.Trim(), Name.Trim(), true) == 0) + return true; + } + return false; + } + + /// + /// Clears all Start N' Fail Times for a process + /// + internal void ClearStartNFailTimes() + { + LastStartedTimes.Clear(); + LastStartedTimes.Clear(); + } + + /// + /// Internal Variable to keep track if this Process is in an Error State + /// + internal bool InErrorState; + + /// + /// Keep track of all Last Failed DT Stamps + /// + internal readonly List LastFailedTimes = new List(); + + /// + /// Keep track of all Last Started DT Stamps + /// + internal readonly List LastStartedTimes = new List(); + + /// + /// Last Failed DT Stamp + /// + internal DateTime LastFailTime + { + get + { + if (LastFailedTimes.Count > 0) + return LastFailedTimes.Last(); + else + return DateTime.MinValue; + } + } + + /// + /// Allow specific amount of time to have passed since Last-Fail + /// + /// timespan that has to have occured since Last-Fail Time + /// true if the Last-Fail Exceeded the specified amount of time, false otherwise + internal bool LastFailTimeTimeoutExceeded(TimeSpan ts) + { + if (LastFailedTimes.Count > 0) + { + TimeSpan diff = DateTime.Now - LastFailTime; + if (diff < ts) + return false; + } + return true; + } + + /// + /// Checks to see if the Number of times the process failed in the given timespan + /// exceeds nMaxTry + /// + /// number of max times it is allowed to fail + /// TimeSpan to subtract from DateTime.Now + /// true if number of times in the given timespan exceeds nMaxTry, false otherwise + internal bool GetFailTimesInGivenTimeSpanMaxTryExceeded(TimeSpan span, uint nMaxTry) + { + if (LastFailedTimes.Count == 0) + return false; + + int nCount = GetFailTimesInGivenTimeSpan(span); + return (nCount > nMaxTry); + } + + /// + /// Last Started DT Stamp + /// + internal DateTime LastStartTime + { + get + { + if (LastStartedTimes.Count > 0) + return LastStartedTimes.Last(); + else + return DateTime.MinValue; + } + } + + /// + /// Allow specific amount of time to have passed since Last-Start + /// + /// timespan that has to have occured since Last-Start Time + /// true if the Last-Start Exceeded the specified amount of time, false otherwise + internal bool LastStartTimeTimeoutExceeded(TimeSpan ts) + { + if (LastStartedTimes.Count > 0) + { + TimeSpan diff = DateTime.Now - LastStartTime; + if (diff < ts) + return false; + } + return true; + } + + /// + /// Checks to see if the Number of times the process started in the given timespan + /// exceeds nMaxTry + /// + /// number of max times it is allowed to have started + /// TimeSpan to subtract from DateTime.Now + /// true if number of times in the given timespan exceeds nMaxTry, false otherwise + internal bool GetStartTimesInGivenTimeSpanMaxTryExceeded(TimeSpan span, uint nMaxTry) + { + if (LastStartedTimes.Count == 0) + return false; + + int nCount = GetStartTimesInGivenTimeSpan(span); + return (nCount > nMaxTry); + } + + /// + /// Add a FailTime to keep track of for the given Service + /// + /// Fail-Time + /// max capacity to store + //internal void AddFailTime(DateTime dtFail, int nMaxCapacity = 1) + internal void AddFailTime(DateTime dtFail, int nMaxCapacity) + { + if (dtFail != DateTime.MinValue) + { + // Make sure to always at least store one + if (nMaxCapacity < 1) + nMaxCapacity = 1; + + // Add and adjust according to capacity + LastFailedTimes.Add(dtFail); + if (LastFailedTimes.Count > nMaxCapacity) + { + int nRemoveCount = nMaxCapacity - LastFailedTimes.Count; + for (int i = 0; i < nRemoveCount; ++i) + LastFailedTimes.RemoveAt(i); + } + } + } + + /// + /// Add a StartTime to keep track of for the given Service + /// + /// Start-Time + /// max capacity to store + //internal void AddStartTime(DateTime dtStart, int nMaxCapacity = 1) + internal void AddStartTime(DateTime dtStart, int nMaxCapacity) + { + if (dtStart != DateTime.MinValue) + { + // Make sure to always at least store one + if (nMaxCapacity < 1) + nMaxCapacity = 1; + + // Add and adjust according to capacity + LastStartedTimes.Add(dtStart); + if (LastStartedTimes.Count > nMaxCapacity) + { + int nRemoveCount = nMaxCapacity - LastStartedTimes.Count; + for (int i = 0; i < nRemoveCount; ++i) + LastStartedTimes.RemoveAt(i); + } + } + } + + /// + /// Retrieves the Number of times the process failed / Started in the given timespan + /// + /// TimeSpan to subtract from DateTime.Now + /// number of times this Process Failed in the given timespan + internal int GetFailTimesInGivenTimeSpan(TimeSpan span) + { + if (LastFailedTimes.Count == 0) + return 0; + + int nCount = 0; + DateTime dtMin = DateTime.Now - span; + foreach (DateTime dt in LastFailedTimes) + { + if (dt >= dtMin) + ++nCount; + } + return nCount; + } + + /// + /// Retrieves the Number of times the process Started in the given timespan + /// + /// TimeSpan to subtract from DateTime.Now + /// number of times this Process Started in the given timespan + internal int GetStartTimesInGivenTimeSpan(TimeSpan span) + { + if (LastStartedTimes.Count == 0) + return 0; + + int nCount = 0; + DateTime dtMin = DateTime.Now - span; + foreach (DateTime dt in LastStartedTimes) + { + if (dt >= dtMin) + ++nCount; + } + return nCount; + } + + #endregion + + #region IComparable Members + + /// + /// Compare the ServiceExe + /// + /// a valid ServiceExe Obj + /// 0 if equal, -1 if obj is smaller, +1 if obj is bigger + public int CompareTo(object obj) + { + if (obj is ServiceExe) + { + ServiceExe s = (ServiceExe)obj; + int nCompare = String.Compare(s.Name.Trim(), this.Name.Trim(), true); + if (nCompare == 0) + { + nCompare = this.AllowRestart.CompareTo(s.AllowRestart); + } + return nCompare; + } + else + { + throw new ArgumentException("object is not a ServiceExe"); + } + } + + #endregion + + #region ICloneable Members + + /// + /// Clones the ServiceExe Object + /// + /// a new ServiceExe Object + public object Clone() + { + ServiceExe service = new ServiceExe(this.Name); + service.AllowRestart = this.AllowRestart; + return service; + } + + #endregion + } + + #region ICloneable Members + + public object Clone() + { + MonitorConfiguration config = new MonitorConfiguration(); + config.MonitoredProcesses = (MonitoredProcessesW) this.MonitoredProcesses.Clone(); + config.MonitoredServices = (MonitoredServicesW) this.MonitoredServices.Clone(); + return config; + } + + #endregion + } + + #endregion + + /// + /// Object is responsible for Loading/Saving the MonitorConfiguration to + /// and From XML File. + /// + public class MonitorDataStore + { + #region Private Members + + private MonitorConfiguration _RunTimeWatchdogXMLConfigurationData = null; + private XSerializer _xmlserializer = null; + private string _dsFileNameNPath = ""; + private DelegateCollection.Void_Param1_Exception_Func _exceptionHandler = null; + private bool _bForceRefreshOnNextRead = false; + private object _lock = new object(); + + #endregion + + #region internal statics + + internal static List MonitoredProcessNamesExclusionList = null; + + #endregion + + #region Constructor + + /// + /// Construct a MonitorDataStore Object, responsible for loading and + /// saving MonitorConfiguration from/to XML. + /// + /// a valid filename and path To load/store MonitorConfiguration + /// List of Process Names that will always be consider excluded * invalid * to add to configuration + /// Thrown if Empty dsFileNameNPath is passed in + //public MonitorDataStore(string dsFileNameNPath, List ExclusionListProcessNames = null, DelegateCollection.Void_Param1_Exception_Func exceptionHandler = null) + public MonitorDataStore(string dsFileNameNPath, List ExclusionListProcessNames, DelegateCollection.Void_Param1_Exception_Func exceptionHandler) + { + if (String.IsNullOrEmpty(dsFileNameNPath)) + throw new ArgumentException("Invalid dsFileNameNPath"); + + _dsFileNameNPath = dsFileNameNPath; + MonitoredProcessNamesExclusionList = ExclusionListProcessNames; + _exceptionHandler = exceptionHandler; + + // Initialize Serializer And Try to Read File configuration + _xmlserializer = new XSerializer(); + _RunTimeWatchdogXMLConfigurationData = _xmlserializer.ReadFromFile(_dsFileNameNPath); + } + + #endregion + + #region Public Static Accessor Methods + + /// + /// Use this function to force a Data Refresh the next time ReadData() is called + /// + public bool ForceRefreshOnNext_ReadData { get { return _bForceRefreshOnNextRead; } set { _bForceRefreshOnNextRead = value; } } + + /// + /// Read the XML Configuration Data (From a File in the System) or from Cache + /// + /// set to true to force a reload of configuration from the disk + /// Exception Handler Function, if an Exception is Thrown + /// an XMLDataStore object or Null if error occured + //public WatchdogConfiguration ReadData(bool bForceRefresh = false) + public MonitorConfiguration ReadData(bool bForceRefresh) + { + try + { + // Force Refresh on next Call? + if (_bForceRefreshOnNextRead) + { + bForceRefresh = true; + _bForceRefreshOnNextRead = false; + } + + // Load Configuration From the XML File + if (bForceRefresh || (_RunTimeWatchdogXMLConfigurationData == null)) + { + // XML could have been modified by the user, hence we it is unverified + MonitorConfiguration UnverifiedConfig = _xmlserializer.ReadFromFile(_dsFileNameNPath); + if(UnverifiedConfig != null) + { + // Make sure that all ProcessExes are Unique (ProcessExe and CommandLine and WorkingDirectory * together * are unique) + MonitorConfiguration VerifiedConfig = new MonitorConfiguration(); + + // calling AddProcessExe() will enforce uniqueness + foreach (MonitorConfiguration.ProcessExe p in UnverifiedConfig.MonitoredProcesses.ProcessExes) + VerifiedConfig.MonitoredProcesses.AddProcessExe(p); + + // calling AddServiceExe() will enforce uniqueness + foreach (MonitorConfiguration.ServiceExe s in UnverifiedConfig.MonitoredServices.ServiceExes) + VerifiedConfig.MonitoredServices.AddServiceExe(s); + + // always use the valid configuration + lock (_lock) + { + _RunTimeWatchdogXMLConfigurationData = VerifiedConfig; + } + + // save the verified configuration xml * potentially fixing any issue that were in it * + SaveData(); + } + } + } + catch (Exception e) { if (_exceptionHandler != null) _exceptionHandler(e); } + + // No File Exists, write out a blank file * and load a default * blank * configuration + // This function should NEVER, EVER pass out null + if (_RunTimeWatchdogXMLConfigurationData == null) + { + SaveData(null); + _RunTimeWatchdogXMLConfigurationData = _xmlserializer.ReadFromFile(_dsFileNameNPath); + } + return _RunTimeWatchdogXMLConfigurationData; + } + + /// + /// Writes the XML Configuration Data passed in out to Disk + /// + /// The XML Data To Write out + /// Exception Handler Function, if an Exception is Thrown + public void SaveData(MonitorConfiguration data) + { + try + { + _xmlserializer.WriteToFile(data, _dsFileNameNPath); + lock (_lock) + { + _RunTimeWatchdogXMLConfigurationData = data; + } + } + catch (Exception e) { if (_exceptionHandler != null) _exceptionHandler(e); } + } + + /// + /// Writes the XML Configuration Data that is currently stored in this Object out to Disk + /// + /// Exception Handler Function, if an Exception is Thrown + public void SaveData() + { + try + { + _xmlserializer.WriteToFile(_RunTimeWatchdogXMLConfigurationData, _dsFileNameNPath); + } + catch (Exception e) { if (_exceptionHandler != null) _exceptionHandler(e); } + } + + #endregion + } +} diff --git a/Net/Emailer.cs b/Net/Emailer.cs new file mode 100644 index 0000000..f7dc564 --- /dev/null +++ b/Net/Emailer.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Mail; +using System.Net; +using webmail = System.Web.Mail; +using Yaulw.Other; + +// Disable System.Web.Mail is depreciated Warning +#pragma warning disable 0618 + +namespace Yaulw.Net +{ + /// + /// Emailer Helps you send Emails using System.Net or System.Web.Mail. + /// System.Web.Mail is depreciated, but currently is the only way to send emails via + /// SSH/SMTP on port 465. + /// + public static class Emailer + { + #region Public Statics + + /// + /// Generic Function to handle Emails + /// + /// specifiy a SMTP Server to use * Required * + /// Email to use to send with * Required * + /// Display name for Sender * Required * + /// ';' seperated list of emails to send to * Required * + /// Email Message Subject * Required * + /// Email Message Text * Required * + /// Smtp Port to use + /// ';' seperated list of emails to cc to + /// true to use SmtpAuthentication, false otherwise + /// Smtp Authentication Username + /// Smtp Authentication Password + /// true to use ssl, false otherwise + /// Defaults to 100 Seconds + /// If an exception occurs this delegate will get called (can be null) + /// true if the Message Send Successfully, false otherwise + public static bool SendEmail(string SmtpServer, string SenderEmail, string SenderDisplayName, string ReceiverEmails, string MessageSubject, string MessageText, uint nPort, string ReceiverCCEmails, bool bUseSmtpAuth, string SmtpUser, string SmtpPassword, bool RequiresSSL, int nTimeOutInSeconds, DelegateCollection.Void_Param1_Exception_Func exceptionFunc) + { + // SSL on Port 25 is Explicit SSL, which is supported by System.Net + if (RequiresSSL && (nPort != 25)) + return SendEmailUsingWebMail(SmtpServer, SenderEmail, SenderDisplayName, ReceiverEmails, MessageSubject, MessageText, nPort, ReceiverCCEmails, bUseSmtpAuth, SmtpUser, SmtpPassword, RequiresSSL, nTimeOutInSeconds, exceptionFunc); + else + return SendEmailUsingNetMail(SmtpServer, SenderEmail, SenderDisplayName, ReceiverEmails, MessageSubject, MessageText, nPort, ReceiverCCEmails, bUseSmtpAuth, SmtpUser, SmtpPassword, RequiresSSL, nTimeOutInSeconds, exceptionFunc); + } + + #endregion + + #region Private Statics + + /// + /// Send an Email * Doesn't Support Implicit SSL (port 465) - ONLY Explicit SSL on 25 + /// + /// specifiy a SMTP Server to use * Required * + /// Email to use to send with * Required * + /// Display name for Sender * Required * + /// ';' seperated list of emails to send to * Required * + /// Email Message Subject * Required * + /// Email Message Text * Required * + /// Smtp Port to use + /// ';' seperated list of emails to cc to + /// true to use SmtpAuthentication, false otherwise + /// Smtp Authentication Username + /// Smtp Authentication Password + /// true to use ssl, false otherwise + /// Defaults to 100 Seconds + /// If an exception occurs, this delegate gets called (can be null) + /// true if the Message Send Successfully, false otherwise + private static bool SendEmailUsingNetMail(string SmtpServer, string SenderEmail, string SenderDisplayName, string ReceiverEmails, string MessageSubject, string MessageText, uint nPort, string ReceiverCCEmails, bool bUseSmtpAuth, string SmtpUser, string SmtpPassword, bool RequiresSSL, int nTimeOutInSeconds, DelegateCollection.Void_Param1_Exception_Func exceptionFunc) + { + try + { + if (String.IsNullOrEmpty(SmtpServer) || String.IsNullOrEmpty(SenderEmail) || String.IsNullOrEmpty(SenderDisplayName) || String.IsNullOrEmpty(ReceiverEmails) || String.IsNullOrEmpty(MessageSubject) || String.IsNullOrEmpty(MessageText)) + return false; + + SmtpClient sMail = new SmtpClient(SmtpServer); + sMail.DeliveryMethod = SmtpDeliveryMethod.Network; + if (bUseSmtpAuth && !String.IsNullOrEmpty(SmtpUser) && !String.IsNullOrEmpty(SmtpPassword)) + { + sMail.UseDefaultCredentials = false; + sMail.Credentials = new NetworkCredential(SmtpUser, SmtpPassword); + } + + // Mail Message + MailMessage msg = new MailMessage(); + msg.Subject = MessageSubject; + // From + msg.From = new MailAddress(SenderEmail, SenderDisplayName); + // To + foreach (string strToEmail in ReceiverEmails.Split(';')) + msg.To.Add(new MailAddress(strToEmail)); + // Body + msg.Body = MessageText; + // CC + if (!String.IsNullOrEmpty(ReceiverCCEmails)) + { + foreach (string strToEmail in ReceiverCCEmails.Split(';')) + msg.CC.Add(new MailAddress(strToEmail)); + } + + // Send it + if (nTimeOutInSeconds < 100) + nTimeOutInSeconds = 100; + sMail.Timeout = (int)TimeSpan.FromSeconds(nTimeOutInSeconds).TotalMilliseconds; + sMail.Port = ((nPort > 0) && (nPort < 65536)) ? (int)nPort : 25; + sMail.EnableSsl = RequiresSSL; + sMail.Send(msg); + return true; + } + catch (Exception e) { if (exceptionFunc != null) exceptionFunc(e); } + return false; + } + + /// + /// Send an Email * Does Support SSL (port 465) via Hacks - System.Web.Mail Space however is depreciated + /// + /// + /// specifiy a SMTP Server to use * Required * + /// Email to use to send with * Required * + /// Display name for Sender * Required * + /// ';' seperated list of emails to send to * Required * + /// Email Message Subject * Required * + /// Email Message Text * Required * + /// Smtp Port to use + /// ';' seperated list of emails to cc to + /// true to use SmtpAuthentication, false otherwise + /// Smtp Authentication Username + /// Smtp Authentication Password + /// true to use ssl, false otherwise + /// Defaults to 100 Seconds + /// If an exception occurs, this delegate gets called (can be null) + /// true if the Message Send Successfully, false otherwise + private static bool SendEmailUsingWebMail(string SmtpServer, string SenderEmail, string SenderDisplayName, string ReceiverEmails, string MessageSubject, string MessageText, uint nPort, string ReceiverCCEmails, bool bUseSmtpAuth, string SmtpUser, string SmtpPassword, bool RequiresSSL, int nTimeOutInSeconds, DelegateCollection.Void_Param1_Exception_Func exceptionFunc) + { + try + { + if (String.IsNullOrEmpty(SmtpServer) || String.IsNullOrEmpty(SenderEmail) || String.IsNullOrEmpty(SenderDisplayName) || String.IsNullOrEmpty(ReceiverEmails) || String.IsNullOrEmpty(MessageSubject) || String.IsNullOrEmpty(MessageText)) + return false; + + string[] Receivers = ReceiverEmails.Split(';'); + foreach (string Receiver in Receivers) + { + webmail.MailMessage Mail = new System.Web.Mail.MailMessage(); + Mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserver"] = SmtpServer; + Mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendusing"] = 2; + Mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserverport"] = nPort.ToString(); + if (RequiresSSL) + Mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpusessl"] = "true"; + if (bUseSmtpAuth) + { + Mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"] = 1; + Mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendusername"] = SmtpUser; + Mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendpassword"] = SmtpPassword; + } + + // Send Message + Mail.To = Receiver; + Mail.From = SenderEmail; + Mail.Subject = MessageSubject; + Mail.Body = MessageText; + webmail.SmtpMail.SmtpServer = SmtpServer; + webmail.SmtpMail.Send(Mail); + } + + return true; + } + catch (Exception e) { if (exceptionFunc != null) exceptionFunc(e); } + return false; + } + + #endregion + } +} diff --git a/Net/IPHostHelper.cs b/Net/IPHostHelper.cs new file mode 100644 index 0000000..d5bb278 --- /dev/null +++ b/Net/IPHostHelper.cs @@ -0,0 +1,832 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using Yaulw.Win32; +using System.Management; +using System.IO; + +namespace Yaulw.Net +{ + /// + /// Helper for common IP / Host Machine + /// + public static class IPHostHelper + { + #region Public consts + + /// + /// This automation URL used to work, now it no longer works :( + /// Caller must set this before calling ExternalIP() Functions + /// + public static string WHAT_IS_MY_IP_AUTOMATION_URL = "http://automation.whatismyip.com/n09230945.asp"; + + #endregion + + #region IPv4 Checkers + + /// + /// Checks to see that the passed in ip is a valid Local IP + /// address that is of version 4 and not a broadcast/loopback/None + /// + /// + /// true to allow Loopback address as valid Local IP + /// + /// + /// IPAddress.IPv6Any = "::"; + /// IPAddress.Any = "0.0.0.0" + /// IPAddress.Broadcast = "255.255.255.255" + /// IPAddress.None = "255.255.255.255" + /// IPAddress.Loopback = "127.0.0.1" + /// + public static bool IsValidIPv4Address(IPAddress ip, bool bAllowLoopback) + { + if (ip != null) + { + if (ip.AddressFamily != AddressFamily.InterNetwork) + return false; + if (ip.AddressFamily.ToString().Contains(IPAddress.IPv6Any.ToString())) + return false; + if (ip == IPAddress.Any) + return false; + if (ip == IPAddress.Broadcast) + return false; + if (ip == IPAddress.None) + return false; + + // Loopback technically is a valid Local IP that can be used to communicate + // to ourselves (on the same machine) + if (ip == IPAddress.Loopback) + return bAllowLoopback; + + // This must be a valid local IPv4 + return true; + } + return false; + } + + /// + /// Checks to see if the passed in ip is in a valid Private subnet (i.e. not public) + /// + /// + /// + /// + /// Private subnets as defined in + /// Loopback address 127.0.0.1/8 + /// Zeroconf/bonjour self assigned addresses 169.254.0.0/16 + /// + public static bool IsIPv4AddressInPrivateSubnet(IPAddress ip) + { + if (!IsValidIPv4Address(ip, true)) + return false; + + String[] netAddrs = { "192.168.0.0", "10.0.0.0", "172.16.0.0", "127.0.0.1", "169.254.0.0" }; + String[] netMasks = { "255.255.0.0", "255.0.0.0", "255.240.0.0", "255.0.0.0", "255.255.0.0" }; + + UInt32 myIP = BitConverter.ToUInt32(ip.GetAddressBytes(), 0); + for (int i = 0; i < netMasks.Length; i++) + { + IPAddress netAddr = IPAddress.Parse(netAddrs[i]); + UInt32 netIP = BitConverter.ToUInt32(netAddr.GetAddressBytes(), 0); + + IPAddress maskAddr = IPAddress.Parse(netMasks[i]); + UInt32 maskIP = BitConverter.ToUInt32(maskAddr.GetAddressBytes(), 0); + + if ((myIP & maskIP) == (netIP & maskIP)) + return true; + } + return false; + } + + /// + /// Compares two IP Addresses, if the Left One is different from the right one, assigns + /// the right one to the LeftOrRight (simple) and returns true. Otherwise assigns left to it and returns false. + /// + public static bool AreIPAddressesDifferent(IPAddress left, IPAddress right, out IPAddress LeftOrRight) + { + LeftOrRight = left; + if (left != null && right != null) + { + if (String.Compare(left.ToString(), right.ToString(), true) != 0) + { + LeftOrRight = right; + return true; + } + } + else if (left == null && right != null) + { + LeftOrRight = right; + return true; + } + return false; + } + + #endregion + + #region Connectivity + + /// + /// Method to check if the Local Computer has a valid Network Connection + /// + /// + public static bool HasConnection() + { + //instance of our ConnectionStatusEnum + Definitions.ConnectionStatusEnum state = 0; + + //call the API + bool bIsConnected = Wininet.InternetGetConnectedState(ref state, 0); + if (!bIsConnected) + return false; + + //check the status, if in offline mode then return false + if (((int)Definitions.ConnectionStatusEnum.INTERNET_CONNECTION_OFFLINE & (int)state) != 0) + return false; + + return true; + } + + /// + /// Method to check to see if local Computer has a valid Network connection + /// and can open the specified ip and port + /// + /// + /// + /// + public static bool HasConnectivity(IPAddress ip, uint port) + { + if(HasConnection() && IsValidIPv4Address(ip, true) && port > 0) + return IsPortOpen(ip, (int) port); + return false; + } + + /// + /// Method to check to see if local Computer has a valid Network connection + /// and can open the specified host and port + /// + /// + /// + /// + /// + public static bool HasConnectivity(string host, uint port, int nTimeout) + { + if (HasConnection() && port > 0) + { + IPAddress ip = GetIpForHost(host); + if(ip != IPAddress.None && IsValidIPv4Address(ip, false)) + return IsPortOpen(ip, (int) port, nTimeout); + } + return false; + } + + /// + /// Retrieves the Servername from a DataBaseConnectionString, if possible, + /// then gets the ip for the server name and tries to open up the port specified + /// + /// pass in either a DataSource or ConnectionString + /// + /// + public static bool HasConnectivity(string DataBaseConnectionString, uint port) + { + if (HasConnection() && !String.IsNullOrEmpty(DataBaseConnectionString)) + { + // Allow the SharedConnectionDataSource to be a ConnectionString also via calling GetDataSource() + DataBaseConnectionString = GetDataSource(DataBaseConnectionString); + + // Retrieve the IP for the Data Source and test the ip and set it in the API + string host = GetServerNameFromADataSource(DataBaseConnectionString); + IPAddress ip = GetIpForHost(host); + if (ip != IPAddress.None) + { + if (IsPortOpen(ip, (int) port)) + return true; + } + } + return false; + } + + #endregion + + #region Core IPHostHelper Functionallity + + /// + /// Converts all incl. Loopback, if contained, valid IP addresses into a String array of IPs + /// + /// + public static string[] ConvertIPArrayToStringArray(IPAddress[] IPs) + { + List ips = new List(); + if (IPs != null && IPs.Length > 0) + { + foreach (IPAddress ip in IPs) + { + if (IsValidIPv4Address(ip, true)) + ips.Add(ip.ToString()); + } + } + return ips.ToArray(); + } + + /// + /// Get all valid IPv4 Local IP addresses besides the loopback address + /// + /// + public static IPAddress[] GetAllLocalIPAddresses() + { + // Get host name + String HostName = Dns.GetHostName(); + IPHostEntry iphostentry = Dns.GetHostEntry(HostName); + + // Enumerate IP addresses + List IPs = new List(); + foreach (IPAddress ipaddress in iphostentry.AddressList) + { + if (IsValidIPv4Address(ipaddress, false)) + IPs.Add(ipaddress); + } + + // Return them as a string[] of IPs + return IPs.ToArray(); + } + + /// + /// Get First Found Local IP Address (or IPAddress.None, if none found) + /// + /// get the first Local IP Address found + public static IPAddress GetFirstLocalIPAddress() + { + IPAddress[] IPs = GetAllLocalIPAddresses(); + if (IPs.Length > 0) + return IPs[0]; + else + return IPAddress.None; + } + + /// + /// Get Nth Found Local IP Address (or first one found, if n > IPs Found) + /// (or IPAddress.None, if none found) + /// + /// get the Nth Local IP Address found + public static IPAddress GetNthLocalIPAddress(int n) + { + IPAddress[] IPs = GetAllLocalIPAddresses(); + if (n >= 0 && IPs.Length < n) + return IPs[n]; + else if (IPs.Length > 0) + return IPs[0]; + else + return IPAddress.None; + } + + /// + /// Check to see if the host passed in is Local + /// + /// can be ip or host name + /// true, if host or ip is local, false otherwise + /// When host is empty + public static bool IsHostOrIPLocal(string host) + { + if (!String.IsNullOrEmpty(host)) + { + IPAddress ip = null; + if (IPAddress.TryParse(host, out ip)) + { + IPAddress[] localIPs = GetAllLocalIPAddresses(); + foreach(IPAddress localip in localIPs) + { + if (localip == ip) + return true; + } + return false; + } + else + { + return (String.Compare(host.Trim(), Dns.GetHostName(), true) == 0); + } + } + throw new ArgumentException("Invalid host"); + } + + /// + /// Method for resolving a Host (DNS Resolve) + /// + /// the host + /// Set to true, if the host must be in the Private IP Range + /// true, if host can be resolved to an IP Address, false otherwise + public static bool CanResolveHost(string host, bool bMustBeInPrivateIPRange) + { + if(!String.IsNullOrEmpty(host)) + { + IPAddress ip = GetIpForHost(host); + if (bMustBeInPrivateIPRange) + return IsIPv4AddressInPrivateSubnet(ip); + return (ip != IPAddress.None); + } + return false; + } + + /// + /// Method for retrieving the IP address for a Host (DNS Resolve) + /// + /// the host we need the ip address for + /// an IPAddress if found, otherwise IPAddress.None + public static IPAddress GetIpForHost(string host) + { + //variable to hold our error message (if something fails) + string errMessage = string.Empty; + + //IPAddress instance for holding the returned host + IPAddress address = IPAddress.None; + + //wrap the attempt in a try..catch to capture + //any exceptions that may occur + try + { + // if it's already an ip address being passed in, + // we are done + if (!IPAddress.TryParse(host, out address)) + { + //get the host IP from the name provided + address = Dns.GetHostEntry(host).AddressList[0]; + + // We must be called for the local host, that + // could explain why it is not a valid IP + if (!IsValidIPv4Address(address, false)) + address = GetFirstLocalIPAddress(); + } + else + { + address = IPAddress.Parse(host); + } + } + catch (Exception) { /* ignore */ } + if (address == null) + return IPAddress.None; + else + return address; + } + + /// + /// Ping a host + /// + /// url/host/ip + /// timeout value in miliseconds + /// number of times to ping + /// the number of successful pings + public static int PingHost(string host, int nTimeout, int nTrys) + { + //string to hold our return messge + //string returnMessage = string.Empty; + + // Check to see if the host passed in is an IP Address + // otherwise, just resolve the host + IPAddress address = null; + if(!IPAddress.TryParse(host, out address)) + address = GetIpForHost(host); + + // Error occured resolving host + if (!IsValidIPv4Address(address, false)) + return 0; + + //set the ping options, TTL 128 + PingOptions pingOptions = new PingOptions(128, true); + + //create a new ping instance + Ping ping = new Ping(); + + //32 byte buffer (create empty) + byte[] buffer = new byte[32]; + + //first make sure we actually have an internet connection + int nPingSuccess = 0; + if (HasConnection()) + { + //here we will ping the host nTrys times + for (int i = 0; i < nTrys; i++) + { + try + { + PingReply pingReply = ping.Send(address, nTimeout, buffer, pingOptions); + if (!(pingReply == null)) + { + switch (pingReply.Status) + { + case IPStatus.Success: + nPingSuccess++; + break; + case IPStatus.TimedOut: + default: + break; + } + } + } + catch (Exception) { /* ignore and continue iterating */ } + } + } + return nPingSuccess; + } + + #endregion + + #region Port Open + + /// + /// Check to see if a port is open for the specified ip + /// + /// + /// + /// + public static bool IsPortOpen(IPAddress ip, int port) + { + bool bCanConnect = false; + if (IsValidIPv4Address(ip, true) && port > 0) + { + try + { + TcpClient tcP = new System.Net.Sockets.TcpClient(); + tcP.Connect(ip, port); + bCanConnect = true; + tcP.Close(); + } + catch (Exception) { /* ignore */ } + } + return bCanConnect; + } + + /// + /// Check to see if a port is open for the specified ip + /// + /// + /// + /// + public static bool IsPortOpen(IPAddress ip, int port, int nTimeout) + { + bool bCanConnect = false; + if (IsValidIPv4Address(ip, true) && port > 0) + { + try + { + TcpClient tcP = new System.Net.Sockets.TcpClient(); + if (nTimeout > 0) + tcP.ReceiveTimeout = nTimeout; + tcP.Connect(ip, port); + bCanConnect = true; + tcP.Close(); + } + catch (Exception) { /* ignore */ } + } + return bCanConnect; + } + + #endregion + + #region Get External IP + + /// + /// Get the External IP + /// + /// Timeout in MiliSeconds + /// a valid external IPAddress or IPAddress.None, if error occured + public static IPAddress GetExternalIP(int pTimeOutMiliSeconds) + { + IPAddress extIP = IPAddress.None; + try + { + using (WebClient wc = new WebClient()) + { + wc.Headers.Add("user-agent", WCHelper.USER_AGENT_GENERIC); + UTF8Encoding utf8 = new UTF8Encoding(); + string ipaddr = null; + bool done = false; + + wc.DownloadDataCompleted += new + DownloadDataCompletedEventHandler((object sender, + DownloadDataCompletedEventArgs e) => + { + ipaddr = utf8.GetString(e.Result); + done = true; + }); + + wc.DownloadDataAsync(new Uri(WHAT_IS_MY_IP_AUTOMATION_URL)); + System.DateTime startTime = System.DateTime.Now; + while (!done) + { + System.TimeSpan sp = System.DateTime.Now - startTime; + + // We should get a response in less than timeout. + // If not, we cancel all and return the internal IP Address + if (sp.TotalMilliseconds > pTimeOutMiliSeconds) + { + done = true; + wc.CancelAsync(); + } + } + + if (IPAddress.TryParse(ipaddr, out extIP)) + return extIP; + } + } + catch { /* ignore */ } + return IPAddress.None; + } + + /// + /// Get the External IP + /// + /// a valid external IPAddress or IPAddress.None, if error occured + public static IPAddress GetExternalIP() + { + string ipaddr = WCHelper.ScreenScrapeFromURL(WHAT_IS_MY_IP_AUTOMATION_URL); + IPAddress extIP = IPAddress.None; + if (IPAddress.TryParse(ipaddr, out extIP)) + return extIP; + return IPAddress.None; + } + + /// + /// Call this to retrieve the external IP Address (also makes sure we can connect + /// to WhatIsMyIp site) + /// + /// + /// Forces 4 checks at 2.5/5.0/7.5/10 seconds timeout values + /// otherwise makes one request defaulting to 5.0 + /// + /// valid external IP Address or null if not found + public static IPAddress GetExternalIP(bool bForceMultipleChecks) + { + if (HasConnection()) + { + IPAddress ipExtIP = null; + if (bForceMultipleChecks) + { + // Check with an timout of 2.5/5.0/7.5/10 seconds + for (int n = 2500; n <= 10000; n = n + 2500) + { + ipExtIP = GetExternalIP(n); + if(ipExtIP != IPAddress.None) + return ipExtIP; + } + } + else + { + // Check with whatever is the default timeout + ipExtIP = Yaulw.Net.IPHostHelper.GetExternalIP(5000); + return ipExtIP; + } + } + return IPAddress.None; + } + + #endregion + + #region UNC and Path Related Windows Network Helpers + + /// + /// Allows us to use either pass in a ConnectionString or a DataSource * Added flexibility * + /// will return the DataSource part of the connection string, if exists, otherwise the connection string + /// + /// pass in either a DataSource or ConnectionString + /// + public static string GetDataSource(string DataBaseConnectionString) + { + if (IsDataBaseConnectionString(DataBaseConnectionString)) + { + String[] tokens = DataBaseConnectionString.Split(';'); + foreach (string Pair in tokens) + { + string[] KeyValuePair = Pair.Split('='); + try + { + string left = KeyValuePair[0]; + string right = KeyValuePair[1]; + string FoundValue = ""; + if (String.Compare(KeyValuePair[0].ToUpper().Trim(), "DATA SOURCE", true) == 0) + { + if (!String.IsNullOrEmpty(right)) + { + FoundValue = right.Trim(); + return FoundValue; + } + } + + } + catch (Exception) { /* ignore and continue iteration */ } + } + } + + // Migth still be a data source just not from a Connection string + // so just return the value trimmed + if (!String.IsNullOrEmpty(DataBaseConnectionString)) + return DataBaseConnectionString.Trim(); + else + return String.Empty; + } + + /// + /// Quick Check to see if a connection string is valid * must have DataSource Key and + /// a Value * in order to be considered a valid Connection String + /// + /// + /// + public static bool IsDataBaseConnectionString(string ConnectionString) + { + if (!String.IsNullOrEmpty(ConnectionString) && + ConnectionString.ToUpper().Contains("DATA SOURCE") && + ConnectionString.Contains("=")) + { + String[] tokens = ConnectionString.Split(';'); + foreach (string Pair in tokens) + { + string[] KeyValuePair = Pair.Split('='); + try + { + string left = KeyValuePair[0]; + string right = KeyValuePair[1]; + if (String.Compare(KeyValuePair[0].ToUpper().Trim(), "DATA SOURCE", true) == 0) + return !String.IsNullOrEmpty(right); + } + catch (Exception) { /* ignore and continue iteration */ } + } + return true; + } + return false; + } + + /// + /// Returns the Server Name from a Database DataSource Connection String + /// + /// + /// [DriveLetter]:\{SomeFileNameNPath} + /// \\{Server]\{SomeFileNameNPath} + /// \\{Server]:[Port]\{SomeFileNameNPath} + /// [Server]\Instance + /// [Server] + /// + /// Server string returned can be a name or an IP address + /// Server Host Name or IP + public static string GetServerNameFromADataSource(string DataSource) + { + if (!String.IsNullOrEmpty(DataSource)) + { + + // Always strip out port in the DataSource *before doing anything* + // only do this for non-local paths + if (DataSource.Contains(":") && DataSource[1] != ':') + { + int nLoc = DataSource.IndexOf(":"); + if (nLoc != -1) + DataSource = DataSource.Substring(0, nLoc); + } + + // Let's begin ... + string Server = String.Empty; + + // Local Medisoft + bool bIsLocalFilePath = DataSource.Contains(@":\"); + if (bIsLocalFilePath) + { + string PathInCaseMapped = ResolveToUNCIfNeeded(DataSource); + + // It's a local or a UNC path + if (!PathInCaseMapped.StartsWith(@"\\")) + { + // If Local file that the host is this computer + return Dns.GetHostName(); + } + else + { + // else it's the other host * so follow UNC logic * + // Stripe out first 2 chars + Server = PathInCaseMapped.Substring(2); + + // Stripe out the rest + int nLoc = Server.IndexOf(@"\"); + if (nLoc != -1) + Server = Server.Substring(0, nLoc); + } + } + + // Remote Medisoft + bool bIsNetworkFilePath = DataSource.StartsWith(@"\\"); + if (bIsNetworkFilePath) + { + // Stripe out first 2 chars + Server = DataSource.Substring(2); + + // Stripe out the rest + int nLoc = Server.IndexOf(@"\"); + if (nLoc != -1) + Server = Server.Substring(0, nLoc); + } + + // It's Lytec! :) + bool bIsSQLServerInstance = !bIsLocalFilePath && !bIsNetworkFilePath; + if (bIsSQLServerInstance) + { + // Stripe out Instance, if it exists + int nLoc = DataSource.IndexOf(@"\"); + if (nLoc != -1) + Server = DataSource.Substring(0, nLoc); + else + Server = DataSource; + } + + return Server; + } + return String.Empty; + } + + /// + /// Determines if the passed in string is a connection string or a data Source + /// and tries to make the best of things to retrieve the host/server name + /// + /// + /// + public static string GetServerNameFromAConnectionString(string ConnectionString) + { + if (IsDataBaseConnectionString(ConnectionString)) + { + string DataSource = GetDataSource(ConnectionString); + return GetServerNameFromADataSource(DataSource); + } + else + { + // Pretentd that it is just a DataSource + return GetServerNameFromADataSource(ConnectionString); + } + } + + /// Resolves the given path to a UNC path, or local drive path. + /// + /// \\server\share\[path] OR [driveletter]:\[path] + public static string ResolveToUNCIfNeeded(string pPath) + { + if (String.IsNullOrEmpty(pPath)) + return String.Empty; + + ManagementObject mo = new ManagementObject(); + + // Already UNC, we are done here + if (pPath.StartsWith(@"\\")) { return pPath; } + + // Get just the drive letter for WMI call + string driveletter = GetDriveLetter(pPath); + + mo.Path = new ManagementPath(string.Format("Win32_LogicalDisk='{0}'", driveletter)); + + // Get the data we need + uint DriveType = System.Convert.ToUInt32(mo["DriveType"]); + string NetworkRoot = System.Convert.ToString(mo["ProviderName"]); + mo = null; + + // Return the root UNC path if network drive, otherwise return the root path to the local drive + if (DriveType == 4) + { + return NetworkRoot + pPath.Substring(2); + } + else + { + return driveletter + pPath.Substring(2); + } + } + + /// Checks if the given path is on a network drive. + /// + /// + public static bool isNetworkDrive(string pPath) + { + ManagementObject mo = new ManagementObject(); + + if (pPath.StartsWith(@"\\")) { return true; } + + // Get just the drive letter for WMI call + string driveletter = GetDriveLetter(pPath); + + mo.Path = new ManagementPath(string.Format("Win32_LogicalDisk='{0}'", driveletter)); + + // Get the data we need + uint DriveType = System.Convert.ToUInt32(mo["DriveType"]); + mo = null; + + return DriveType == 4; + } + + #endregion + + #region Private Helpers + + /// Given a path will extract just the drive letter with volume separator. + /// + /// C: + private static string GetDriveLetter(string pPath) + { + if (pPath.StartsWith(@"\\")) { throw new ArgumentException("A UNC path was passed to GetDriveLetter"); } + return Directory.GetDirectoryRoot(pPath).Replace(Path.DirectorySeparatorChar.ToString(), ""); + } + + #endregion + } +} diff --git a/Net/RouterHelper.cs b/Net/RouterHelper.cs new file mode 100644 index 0000000..4cf01b2 --- /dev/null +++ b/Net/RouterHelper.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Yaulw.Net +{ + + public static class RouterHelper + { + + + } +} diff --git a/Net/WCHelper.cs b/Net/WCHelper.cs new file mode 100644 index 0000000..07b6be9 --- /dev/null +++ b/Net/WCHelper.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using System.IO; + +namespace Yaulw.Net +{ + /// + /// WebClient Helper class to get commen stuff done + /// + public static class WCHelper + { + public const string USER_AGENT_IE8 = "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .Net CLR 3.3.69573; .NET CLR 1.0.3705; WOW64; rv:12.0; en-US) Gecko/20100101 Firefox/12.0"; + public const string USER_AGENT_GENERIC = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0"; + + /// + /// Download the specified URL file to the local path + /// + /// + /// + /// + /// + public static bool DownloadFileFromURL(string URLFileNameNPath, string LocalFileNameNPath, bool bOverwriteExisting) + { + if(!String.IsNullOrEmpty(URLFileNameNPath) && !String.IsNullOrEmpty(LocalFileNameNPath)) + { + try + { + using (WebClient fileReader = new WebClient()) + { + //string filename = URLFileNameNPath.Substring(URLFileNameNPath.LastIndexOf("/"), URLFileNameNPath.Length); + if (!System.IO.File.Exists(LocalFileNameNPath) || bOverwriteExisting) + fileReader.DownloadFile(URLFileNameNPath, LocalFileNameNPath); + } + return true; + } + catch (Exception) { /* ignore */ } + } + return false; + } + + /// + /// ScreenScrape the Text from the URL + /// + /// + /// + public static string ScreenScrapeFromURL(string URL) + { + if (!String.IsNullOrEmpty(URL)) + { + try + { + using (WebClient fileReader = new WebClient()) + { + fileReader.Headers.Add("user-agent", USER_AGENT_GENERIC); + using (Stream data = fileReader.OpenRead(URL)) + using (StreamReader sr = new StreamReader(data)) + { + string str = sr.ReadToEnd(); + return str; + } + } + } + catch (Exception) { /* ignore */ } + } + return ""; + } + + + } +} diff --git a/Net/WSCaller.cs b/Net/WSCaller.cs new file mode 100644 index 0000000..56abb53 --- /dev/null +++ b/Net/WSCaller.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Refl = System.Reflection; +using System.Net; +using System.Web.Services.Description; +using System.CodeDom; +using System.CodeDom.Compiler; +using Yaulw.Other; + +namespace Yaulw.Net +{ + /// + /// Class to Make Soap Web Services Calls Dynamically (Works only for Microsoft-based Web Services it seems) + /// + public class WSCaller + { + #region Private Members + + private string _ServiceName = ""; // example: "WebService" + private string _Url = ""; // example: "http://webservice.com/WebService/WebService.asmx" + + private Refl.Assembly _webServiceAssembly = null; + + // available services + private List _services = new List(); + + // available types + private Dictionary _availableTypes = new Dictionary(); + + private bool _InvokeMethodErrorWasThrown = false; + + #endregion + + #region Construction + + /// + /// Creates the service invoker using the specified web service. + /// + /// Name of Web Service + /// Url of Web Service + public WSCaller(string ServiceName, string ServiceUrl) + { + if (!String.IsNullOrEmpty(ServiceName) && !String.IsNullOrEmpty(ServiceUrl)) + { + // Store Service Specifics + _ServiceName = ServiceName; + _Url = ServiceUrl; + + // Try Creating the WS Assembly + CreateAssemblyFromWebServiceDescription(); + } + else + throw new ArgumentNullException("Service Name or Service Url can not be Null or Empty"); + } + + #endregion + + #region Public Methods + + /// + /// Check to see if the Web Service Is Available + /// + public bool WebServiceAvailable + { + get + { + CreateAssemblyFromWebServiceDescription(); + return (_webServiceAssembly != null); + } + } + + /// + /// Make the Soap Method Call + /// + /// Method Name to call + /// parameters to use on call + /// true if called successfully, false otherwise + /// the return value of the Method + public T MakeSoapMethodCall(string MethodName, object[] parameters, out bool bCallSuccess, DelegateCollection.Void_Param1_Exception_Func exception) + { + bCallSuccess = false; + try + { + List Methods = EnumerateServiceMethods(_ServiceName); + if (Methods.Count == 0) + return default(T); + + // Try making a CheckActive Call + T result = InvokeMethod(_ServiceName, MethodName, parameters); + bCallSuccess = true; + return result; + } + catch (Exception e) { _InvokeMethodErrorWasThrown = true; if (exception != null) exception(e); } + return default(T); + } + + #endregion + + #region Private Helpers + + /// + /// create an assembly from the web service description (WSDL) + /// + private void CreateAssemblyFromWebServiceDescription() + { + // Error Occured upon invoke, try rebuilding the WS + if (_InvokeMethodErrorWasThrown && _webServiceAssembly != null) + _webServiceAssembly = null; + + // In Order to properly build/rebuild the WS must be available + if (_webServiceAssembly == null) + { + _webServiceAssembly = BuildAssemblyFromWSDL(new Uri(_Url)); + if (_webServiceAssembly != null) + { + // Reset + _InvokeMethodErrorWasThrown = false; + + // see what service types are available + Type[] types = _webServiceAssembly.GetExportedTypes(); + + // and save them + foreach (Type type in types) + { + _services.Add(type.FullName); + _availableTypes.Add(type.FullName, type); + } + } + } + } + + /// + /// Gets a list of all methods available for the specified service. + /// + /// + /// + private List EnumerateServiceMethods(string serviceName) + { + List methods = new List(); + + if (!_availableTypes.ContainsKey(serviceName)) + { + throw new Exception("Service Not Available"); + } + else + { + Type type = _availableTypes[serviceName]; + + // only find methods of this object type (the one we generated) + // we don't want inherited members (this type inherited from SoapHttpClientProtocol) + foreach (Refl.MethodInfo minfo in type.GetMethods(Refl.BindingFlags.Instance | Refl.BindingFlags.Public | Refl.BindingFlags.DeclaredOnly)) + methods.Add(minfo.Name); + + return methods; + } + } + + /// + /// Invokes the specified method of the named service. + /// + /// The expected return type. + /// The name of the service to use. + /// The name of the method to call. + /// The arguments to the method. + /// The return value from the web service method. + private T InvokeMethod(string serviceName, string methodName, params object[] args) + { + // create an instance of the specified service + // and invoke the method + object obj = null; + Type type = null; + try + { + obj = _webServiceAssembly.CreateInstance(serviceName); + type = obj.GetType(); + } + catch (Exception e) + { + throw new Exception(e.Message + " " + "Creation of webServiceAssembly Failed"); + } + + // Find the Service Point for the Web Service! ~Fix 100 - HTTP 417 Error + ServicePoint servicePoint = ServicePointManager.FindServicePoint(new Uri(_Url)); + if (servicePoint.Expect100Continue == true) + servicePoint.Expect100Continue = false; + + return (T)type.InvokeMember(methodName, Refl.BindingFlags.InvokeMethod, null, obj, args); + } + + /// + /// Builds an assembly from a web service description. + /// The assembly can be used to execute the web service methods. + /// + /// Location of WSDL (only pass in the main asmx url without the ?wdsl). + /// A web service assembly. + private Refl.Assembly BuildAssemblyFromWSDL(Uri webServiceUri) + { + try + { + if (String.IsNullOrEmpty(webServiceUri.ToString())) + throw new Exception("Web Service Not Found"); + + ServiceDescriptionImporter descriptionImporter = BuildServiceDescriptionImporter((webServiceUri.ToString() + "?wsdl")); + return CompileAssembly(descriptionImporter); + } + catch (Exception) + { + return null; + } + } + + /// + /// Builds the web service description importer, which allows us to generate a proxy class based on the + /// content of the WSDL described by the XmlTextReader. + /// + /// The http location of the WSDL + /// A ServiceDescriptionImporter that can be used to create a proxy class. + private ServiceDescriptionImporter BuildServiceDescriptionImporter(string wsdlUrl) + { + try + { + WebClient http = new WebClient(); + ServiceDescription serviceDescription = ServiceDescription.Read(http.OpenRead(wsdlUrl)); + + // build an importer, that assumes the SOAP protocol, client binding, and generates properties + ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter(); + descriptionImporter.ProtocolName = "Soap"; + descriptionImporter.AddServiceDescription(serviceDescription, null, null); + descriptionImporter.Style = ServiceDescriptionImportStyle.Client; + descriptionImporter.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties; + return descriptionImporter; + } + catch (Exception) + { + return null; + } + } + + /// + /// Compiles an assembly from the proxy class provided by the ServiceDescriptionImporter. + /// + /// + /// An assembly that can be used to execute the web service methods. + private Refl.Assembly CompileAssembly(ServiceDescriptionImporter descriptionImporter) + { + try + { + // a namespace and compile unit are needed by importer + CodeNamespace codeNamespace = new CodeNamespace(); + CodeCompileUnit codeUnit = new CodeCompileUnit(); + codeUnit.Namespaces.Add(codeNamespace); + + ServiceDescriptionImportWarnings importWarnings = descriptionImporter.Import(codeNamespace, codeUnit); + if (importWarnings == 0) // no warnings + { + // create a c# compiler + CodeDomProvider compiler = CodeDomProvider.CreateProvider("CSharp"); + + // include the assembly references needed to compile + string[] references = new string[2] { "System.Web.Services.dll", "System.Xml.dll" }; + + CompilerParameters parameters = new CompilerParameters(references); + + // compile into assembly + CompilerResults results = compiler.CompileAssemblyFromDom(parameters, codeUnit); + + foreach (CompilerError oops in results.Errors) + { + // trap these errors and make them available to exception object + throw new Exception("Compilation Error Creating Assembly"); + } + + // all done.... + return results.CompiledAssembly; + } + else + { + // warnings issued from importers, something wrong with WSDL + throw new Exception("Invalid WSDL"); + } + } + catch (Exception) + { + return null; + } + } + + #endregion + } +} diff --git a/Other/CMDLine.cs b/Other/CMDLine.cs new file mode 100644 index 0000000..2c77066 --- /dev/null +++ b/Other/CMDLine.cs @@ -0,0 +1,423 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections.Specialized; + +using Yaulw.Tools; + +namespace Yaulw.Other +{ + /// + /// CommandLine Arguments Parser for an Application + /// + /// + /// internal CMDline cmdline = new CMDline(typeof(App.CommandLine_Option), typeof(App.CommandLine_Flag)); + /// + /// public enum CommandLine_Flag + /// { + /// START, + /// STOP, + /// SHOW, + /// } + /// + /// public enum CommandLine_Option + /// { + /// ADD_SOMETHING, + /// REMOVE_SOMETHING, + /// } + /// + /// cmdline.Parse(e.Args); + /// + /// if (cmdline.HasParams) + /// { + /// if (App.cmdline.ShowHelp || !App.cmdline.ParamsValid) + /// { + /// ShowCommandLineHelp(); + /// return; + /// } + /// + /// string AddSomething = cmdline.GetOptionValue(CommandLine_Option.ADD_SOMETHING, ""); // + /// } + /// + /// + /// + public class CMDline + { + #region private Variables + + // Flag or Option Specifiers CONSTS + private const String FLAG_OR_OPTION_SPECIFIER = "-/"; + private const String HELP_FLAG_SPECIFIER = "?"; + + // Private Parsing Enums + private Type _CMDLineOptions = null; + private Type _CMDLineFlags = null; + + // Private Parsing Data Structures + private Stack _stringStack = new Stack(); + private StringDictionary _stringDictionary = new StringDictionary(); + + // Keep track of the last parsed args as an array + private string[] _lastparsedArgs = null; + + #endregion + + #region Construction + + /// + /// The Command Line Class needs to know what Options and Flags to Parse for + /// + /// Pass an Enum used to determine CommandLine Options + /// Pass an Enum used to determine CommandLine Flags + public CMDline(Type Enum_CMDLineOptions, Type Enum_CMDLineFlags) + { + if (Enum_CMDLineOptions.IsEnum && Enum_CMDLineFlags.IsEnum) + { + _CMDLineOptions = Enum_CMDLineOptions; + _CMDLineFlags = Enum_CMDLineFlags; + } + else + throw new ArgumentException("Both CMDLineOptions and CMDLineFlags must be Enums"); + } + + #endregion + + #region Public Properties + + /// + /// True if User Passed in any Parameters + /// + public bool HasParams { get { return (_stringDictionary.Count >= 1); } } + + /// + /// True if User requested Command-Line Help + /// + public bool ShowHelp { get { return GetFlagOrOptionValueBool(HELP_FLAG_SPECIFIER); } } + + /// + /// True if all Parameters parsed are valid, False otherwise + /// + public bool ParamsValid + { + get + { + if (!HasParams) + return true; + + string[] OptionNames = Enum.GetNames(_CMDLineOptions); + string[] FlagNames = Enum.GetNames(_CMDLineFlags); + + // Get All Flags and Options + List AllFlagsAndOptions = new List(); + foreach (string Option in OptionNames) + AllFlagsAndOptions.Add(Option.ToLower()); + foreach (string Flag in FlagNames) + AllFlagsAndOptions.Add(Flag.ToLower()); + + // Verify the Parameters + bool InvalidParamFound = false; + foreach (string key in _stringDictionary.Keys) + { + InvalidParamFound = (key != HELP_FLAG_SPECIFIER) && !AllFlagsAndOptions.Contains(key); + if (InvalidParamFound) + break; + } + return !InvalidParamFound; + } + } + + #endregion + + #region Public Methods + + /// + /// Main Entry Function to Retrieve an Option Value + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// option to retrieve (From CMDLineOptions Enum) + /// Default value to use if nothing was retrieved * Error occured * + /// value or default value, if not found + public T GetOptionValue(Enum option, T DefaultValue) + { + T RetVal = DefaultValue; + string StringVal = GetFlagOrOptionValueStr(option.ToString()); + try + { + if (ObjTool.IsNotNullAndNotEmpty(StringVal)) + { + RetVal = ObjTool.ConvertStringToObj(StringVal); + } + } + catch (Exception) { /* ignore */ } + return RetVal; + } + + /// + /// Main Entry Function to Retrieve a Flag Value + /// + /// flag enum to retrieve (From CMDLineFlags Enum) + /// Bool Value found or false if not found + public bool GetFlagValue(Enum flag) + { + return GetFlagOrOptionValueBool(flag.ToString()); + } + + /// + /// Returns the Last Parsed Arguments as a string + /// + /// returns last parsed args or String.Empty if none + public string ParsedArgs() + { + if (_lastparsedArgs != null && _lastparsedArgs.Length > 0) + { + StringBuilder sb = new StringBuilder(); + foreach (string s in _lastparsedArgs) + { + sb.Append(s); + sb.Append(" "); + } + + sb.Remove(sb.Length - 1, 1); // remove trailing " " + return sb.ToString(); + } + + return string.Empty; + } + #endregion + + #region Private Helper Functions + + /// + /// Main Function used to Retrieve a String Value + /// + /// Flag or Option to get Value for + /// Value or Empty if not found + private string GetFlagOrOptionValueStr(string FlagOrOption) + { + // If there is a FLAG_OR_OPTION_CHARS, stripe it + if (IsFlagOrOption(FlagOrOption)) + FlagOrOption = GetFlagOrOption(FlagOrOption); + + FlagOrOption = FlagOrOption.ToLower(); + if (_stringDictionary.ContainsKey(FlagOrOption)) + return _stringDictionary[FlagOrOption]; + return String.Empty; + } + + /// + /// Main Function used to Retrieve a Bool Value + /// + /// Flag or Option to get Value for + /// True or False if not found + private bool GetFlagOrOptionValueBool(string FlagOrOption) + { + try + { + string Value = GetFlagOrOptionValueStr(FlagOrOption); + if (!String.IsNullOrEmpty(Value)) + { + bool bValue = bool.Parse(Value); + return bValue; + } + } + catch (Exception) { /*Ignore*/ } + return false; + } + + #endregion + + #region Parsing Methods + + /// true if the param is a flag or option + private bool IsFlagOrOption(string arg) + { + return (FLAG_OR_OPTION_SPECIFIER.Contains(arg[0].ToString())); + } + + /// the Value of a Param + private string GetFlagOrOption(string arg) + { + return arg.Substring(1); + } + + /// + /// Main Entry Function used to Parse CommandLine parameters + /// + /// command line arguments + /// true if any parsing occured, false otherwise + public bool Parse(string[] args) + { + return Parse(args, true); + } + + /// + /// Main Entry Function used to Parse CommandLine parameters + /// + /// command line arguments + /// Allows you to not parse Options as True, if in case the option is blank it will just be considered a blank option/flag + /// true if any parsing occured, false otherwise + public bool Parse(string[] args, bool bAlwaysParseOptionsAsTrue) + { + if (args == null || args.Length <= 0) + return false; + + _stringDictionary.Clear(); + _stringStack.Clear(); + _lastparsedArgs = args; + foreach (string arg in args) + { + if (!String.IsNullOrEmpty(arg)) + { + if (IsFlagOrOption(arg)) + { + _stringStack.Push(GetFlagOrOption(arg).ToLower()); + if (bAlwaysParseOptionsAsTrue) + _stringDictionary[GetFlagOrOption(arg).ToLower()] = "TRUE"; + } + else + { + // must have a flag or option on the stack, + // if it does we use it + if (_stringStack.Count >= 1) + { + _stringDictionary[_stringStack.Pop()] = Parse_ArgCleanup(arg); + } + } + } + } + _stringStack.Clear(); + return true; + } + + /// + /// Used only when cmd is constructed with (null, null), + /// and the cmd.Parse function is called. will iterate thru the internal + /// dictionary and stack and make sure that the command string / line items + /// are valid + /// + /// + public string MakeValidCmdStrFromNewlyParsedCmdLine() + { + StringBuilder sb = new StringBuilder(); + foreach (string key in _stringDictionary.Keys) + { + sb.Append(FLAG_OR_OPTION_SPECIFIER[0]); + sb.Append(MakeValidCmdStr(key)); + sb.Append("="); + sb.Append(_stringDictionary[key]); + sb.Append(""); + } + + foreach (string flag in _stringStack) + { + sb.Append(FLAG_OR_OPTION_SPECIFIER[0]); + sb.Append(MakeValidCmdStr(flag)); + sb.Append(""); + } + + // remove trailing space + sb = sb.Remove(sb.Length - 1, 1); + return sb.ToString(); + } + + /// + /// It appears that the Command-Line can foul us up sometimes when passing + /// certain arguments. This function deals with these special cases. + /// + /// arg to check/poss.clean up + /// cleaned up arg, if needed + private string Parse_ArgCleanup(string arg) + { + if (!String.IsNullOrEmpty(arg) && + (arg.Length > 2) && + (arg[arg.Length - 1] == '"')) // when passing a \ at the end of the command-line with a " + { + arg = arg.Remove(arg.Length - 1); + arg += "\\"; + } + return arg; + } + + #endregion + + #region Static Generate CmdLine Method + + /// + /// Generates a CommandLine Parameter String from the Options/Flags Given + /// + /// StringDictionary that contains all the Options, to use + /// List that contains all the Flags to use + /// a string that can be passed via the commandLine + public static string GenerateCmdLine(StringDictionary Options, List Flags) + { + string CmdLine = String.Empty; + + // Iterate Options, and add them + if (Options != null && Options.Count > 0) + { + foreach (string Key in Options.Keys) + { + string cmdKey = MakeValidCmdStr(Key); + string cmdValue = Options[Key]; + CmdLine = CmdLine + FLAG_OR_OPTION_SPECIFIER[0] + cmdKey + " " + "\"" + cmdValue + "\"" + " "; + } + } + + // Iterate Flags, and add them + if (Flags != null && Flags.Count > 0) + { + foreach (string Flag in Flags) + { + string cmdFlag = MakeValidCmdStr(Flag); + CmdLine = CmdLine + FLAG_OR_OPTION_SPECIFIER[0] + cmdFlag + " "; + } + } + + // Return the generated Commmand Line + return CmdLine; + } + + /// + /// Ensures Integrity with the Generated CMDLine string that Keys don't + /// contain Illegal Characters + /// + /// a Key to make sure it is valid + /// a valid Key + private static string MakeValidCmdStr(string Key) + { + if (!String.IsNullOrEmpty(Key)) + { + //":\\ _!@#$%^&()-+{}[],.;`~"; + Key = Key.Replace(" ", "_"); + Key = Key.Replace(":", "_"); + Key = Key.Replace("\\", "_"); + Key = Key.Replace("/", "_"); + Key = Key.Replace("!", "_"); + Key = Key.Replace("@", "_"); + Key = Key.Replace("#", "_"); + Key = Key.Replace("$", "_"); + Key = Key.Replace("%", "_"); + Key = Key.Replace("^", "_"); + Key = Key.Replace("&", "_"); + Key = Key.Replace("(", "_"); + Key = Key.Replace(")", "_"); + Key = Key.Replace("-", "_"); + Key = Key.Replace("+", "_"); + Key = Key.Replace("{", "_"); + Key = Key.Replace("}", "_"); + Key = Key.Replace("[", "_"); + Key = Key.Replace("]", "_"); + Key = Key.Replace(",", "_"); + Key = Key.Replace(".", "_"); + Key = Key.Replace(";", "_"); + Key = Key.Replace("'", "_"); + Key = Key.Replace("~", "_"); + return Key; + } + return String.Empty; + } + + #endregion + } +} diff --git a/Other/CMDcommands.cs b/Other/CMDcommands.cs new file mode 100644 index 0000000..a72d959 --- /dev/null +++ b/Other/CMDcommands.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Yaulw.Other +{ + /// + /// Commonly used Batch/CMD command Line Command + /// + public static class CMDcommands + { + #region CMDCommands + + /// + /// map a credential to a network path + /// + /// network path + /// user + /// password + /// Command-Line Command + static public string net_use(string path, string user, string pwd) + { + return ("net use " + path + " /User:" + user + " " + pwd); + } + + /// + /// map a credential and drive letter to a network path + /// + /// network path + /// drive letter to map + /// user + /// password + /// + /// Command-Line Command + static public string net_use_persist(string path, string driveletter, string user, string pwd) + { + return ("net use " + driveletter + ": " + path + " /User:" + user + " " + pwd + " PERSISTENT:YES"); + } + + /// + /// Delete a Network Credential from a network path + /// + /// network path + /// Command-Line Command + static public string net_delete(string path) + { + return ("net use /delete " + path); + } + + /// + /// Delete a Drive Letter and Network Credential from a network path + /// + /// + /// Command-Line Command + static public string net_delete_persist(string driveletter) + { + return ("net use /delete " + driveletter + ":"); + } + + #endregion + + #region PSTools + + /// + /// Commands for System Internals PSExec Tools + /// + public static class PSTools + { + /// + /// PSExec is a System Internals Remote Execution CommandLine tool. + /// Use this to execute a .bat/.cmd file on the other computer + /// + /// Name of computer + /// .bat or .cmd file to execute + /// user + /// password + /// true to allow remote process to interact with desktop + /// Command-Line Command + static public string ps_exec(string computer, string file, string user, string pwd, bool interactive) + { + string Show = ""; + if (interactive) + Show = "-i "; + + if (file.ToLower().Contains(".bat") || file.ToLower().Contains(".cmd")) + { + // copy .bat or .cmd file over + return (@"psexec.exe \\" + computer + " -u " + user + " -p " + pwd + " -c -f -e -d " + Show + "/accepteula " + '\"' + file + '\"'); + } + else + { + // doesn't copy over, executes command from location, don't wait for process to terminate + return (@"psexec.exe \\" + computer + " -u " + user + " -p " + pwd + " -d -e " + Show + "/accepteula " + '\"' + file + '\"'); + } + + } + } + + #endregion + } +} diff --git a/Other/CMDexecute.cs b/Other/CMDexecute.cs new file mode 100644 index 0000000..cb2fff2 --- /dev/null +++ b/Other/CMDexecute.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +using Yaulw.File; + +namespace Yaulw.Other +{ + /// + /// Usefull for quickly generating a .bat file and executing it (without a command window) + /// + public static class CMDexecute + { + #region Public Static Methods + + /// + /// Executes the passed in command on the command line * No Command Window Created * + /// + /// a command-line command + /// true to wait till process ends, false otherwise + /// If true, will set "RunAs" For the Process, to Run as Administrator + public static void cmd(string command, bool bWait = true, bool bRunAs = false) + { + FileWriter fileW = new FileWriter(String.Empty, "bat"); + fileW.DeleteFile(); + fileW.WriteLineUTF8(command); + execBatFile(fileW.FileNameNPath, bWait, bRunAs); + fileW.DeleteFile(); + } + + /// + /// Executes the passed in commands on the command line * No Command Window Created * + /// + /// command-line commands + /// true to wait till process ends, false otherwise + /// If true, will set "RunAs" For the Process, to Run as Administrator + public static void cmd(string[] commands, bool bWait = true, bool bRunAs = false) + { + FileWriter fileW = new FileWriter(String.Empty, "bat"); + fileW.DeleteFile(); + foreach (string command in commands) + fileW.WriteLineUTF8(command); + execBatFile(fileW.FileNameNPath, bWait, bRunAs); + fileW.DeleteFile(); + } + + #endregion + + #region Private Static Helpers + + /// + /// Executes the Batch file via CMD.exe, by starting the CMD.exe Process with no Window + /// + /// File (.bat) scrip to execute + /// true to wait till process ends, false otherwise + private static void execBatFile(string FileNameNPath, bool bWait = true, bool bUseRunAs = false) + { + if (!String.IsNullOrEmpty(FileNameNPath) && System.IO.File.Exists(FileNameNPath)) + { + //The "/C" Tells Windows to Run The Command then Terminate + string strCmdLine = "/C " + '\"' + FileNameNPath + '\"'; + string WindowsSystem32Folder = System.Environment.GetFolderPath(Environment.SpecialFolder.System); + System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo((WindowsSystem32Folder + "\\" + "CMD.exe"), strCmdLine); + //startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; + startInfo.CreateNoWindow = true; + startInfo.UseShellExecute = false; + startInfo.WorkingDirectory = Path.GetDirectoryName(FileNameNPath); + if (bUseRunAs) + startInfo.Verb = "runas"; + + // Start the Cmd.exe Process + System.Diagnostics.Process p1; + p1 = System.Diagnostics.Process.Start(startInfo); + if(bWait) + p1.WaitForExit(); + } + } + + #endregion + } +} diff --git a/Other/DelegateCollection.cs b/Other/DelegateCollection.cs new file mode 100644 index 0000000..ae53504 --- /dev/null +++ b/Other/DelegateCollection.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; + +namespace Yaulw.Other +{ + /// + /// Common Delegates * Useful for Dispatching * + /// + public static class DelegateCollection + { + // 0 Params Functions + public delegate void Void_Func(); + public delegate bool Bool_Func(); + public delegate int Int_Func(); + public delegate object Obj_Func(); + public delegate DateTime DateTime_Func(); + + // Void Ret - 1 Params Functions + public delegate void Void_Param1_String_Func(string str1); + public delegate void Void_Param1_Int_Func(int int1); + public delegate void Void_Param1_DateTime_Func(DateTime dt1); + public delegate void Void_Param1_Exception_Func(Exception ex1); + + // Bool Ret - 1 Params Functions + public delegate bool Bool_Param1_String_Func(string str1); + public delegate bool Bool_Param1_Window_Func(Window window); + + // String Ret - 1 Params Functions + public delegate string String_Param1_Bool_Func(bool bool1); + public delegate string String_Param1_String_Func(string str1); + + } +} diff --git a/Other/Installer.cs b/Other/Installer.cs new file mode 100644 index 0000000..0126b9b --- /dev/null +++ b/Other/Installer.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using Yaulw.Process; + +namespace Yaulw.Other +{ + public static class Installer + { + + /// + /// Run a command on the commandline * Hidden * + /// + /// cmd to run + public static string RunCmdLine(string cmdline) + { + string result = PStarter.RunDosCommand(cmdline); + return result; + } + + /// + /// To grant the specified User or group Full Control permissions to the folder and its contents + /// + /// full path to folder/directory + /// domainname\administrator, any windows user or group + /// + public static bool GrantFullPermissionToFolderForUserOrGroup(string FolderNPath, string UserOrGroup) + { + if (Directory.Exists(FolderNPath)) + { + string command = String.Format("cacls \"{0}\" /t /e /g {1}:f", FolderNPath, UserOrGroup); + string strResult = RunCmdLine(command); + if (strResult.Contains("Invalid arguments.")) + return false; + else + return true; + } + return false; + } + } +} diff --git a/Other/OSInfo.cs b/Other/OSInfo.cs new file mode 100644 index 0000000..17af845 --- /dev/null +++ b/Other/OSInfo.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Yaulw.Win32; + +namespace Yaulw.Other +{ + #region OSInfo Enums + + /// + /// Windows OS Types + /// + public enum OStype + { + WIN_2000 = 4, + WIN_2003 = 5, + WIN_VISTA = 6, + WIN_7 = 7, + WIN_8 = 8, + WIN_UNKNOWN = -1, + } + + #endregion + + /// + /// This class provides OS Specific and Version information to the caller + /// + public static class OSInfo + { + #region Public Properties + + /// + /// Get OSType and Version Information + /// + public static int Major { get; private set; } + public static int Minor { get; private set; } + public static OStype OS { get; private set; } + + /// + /// Set Supported Version for an Application + /// + public static OStype MinVersionSupported { get; set; } + public static OStype MaxVersionSupported { get; set; } + + /// + /// Is this OS Version Supported * Min/Max Should be set by Application * + /// + public static bool IsSupportedOS + { + get + { + if (MinVersionSupported != OStype.WIN_UNKNOWN && MaxVersionSupported != OStype.WIN_UNKNOWN) + return ((int) OS >= (int)MinVersionSupported) && ((int) OS <= (int)MaxVersionSupported); + else + return true; + } + } + + /// + /// Is Vista/Win7's Aero Enabled + /// + public static bool IsAero + { + get + { + if (((int)OS >= (int)OStype.WIN_VISTA) || ((int)OS <= (int)OStype.WIN_7)) + { + bool IsAero = false; + if (uxDwm.DwmIsCompositionEnabled(ref IsAero) < 0) + return false; + else + return IsAero; + } + return false; + } + } + + /// + /// Is WindowsXP/2003 Composition Enabled + /// + public static bool IsXPComposition + { + get + { + if ((int)OS == (int)OStype.WIN_2003) + return uxDwm.IsAppThemed(); + return false; + } + } + + #endregion + + #region Construction + + /// + /// Constructor + /// + static OSInfo() + { + // Get the OS Version + Major = System.Environment.OSVersion.Version.Major; + Minor = System.Environment.OSVersion.Version.Minor; + + // Parse the OS Version + try { OSInfo.OS = (OStype)Enum.Parse(typeof(OStype), Major.ToString()); } + catch (Exception) { OSInfo.OS = OStype.WIN_UNKNOWN; } + + // Set Min/Max to Unknown + MinVersionSupported = OStype.WIN_UNKNOWN; + MaxVersionSupported = OStype.WIN_UNKNOWN; + } + + #endregion + } +} diff --git a/Other/StackWalker.cs b/Other/StackWalker.cs new file mode 100644 index 0000000..314a673 --- /dev/null +++ b/Other/StackWalker.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Diagnostics; +using System.Reflection; + +namespace Yaulw.Other +{ + /// + /// Use the StackWalker to Find out what Method/Type Called you. + /// This is useful for logging functions to know what method/type called the Logging Method. + /// + public static class StackWalker + { + /// + /// Hard-Coded in Stack Frame count depending on how this Class is getting called, + /// and how many frames we have to go up / down (use nPlusMinus) + /// + public const int DEFAULT_STACK_FRAME_COUNT = 3; + + /// + /// Use this to get the MethodName from the CallStack. + /// This allows the calling method to know which method called it + /// + /// Use this to add/substract from the base stack level you want to retrieve + /// Returns the MethodName as specified by the CallStack + public static string GetMethodNameFromStack(int nPlusMinus = 0) + { + StackTrace stackTrace = new StackTrace(); + StackFrame stackFrame; + MethodBase stackFrameMethod; + + stackFrame = stackTrace.GetFrame(DEFAULT_STACK_FRAME_COUNT + nPlusMinus); + stackFrameMethod = stackFrame.GetMethod(); + return stackFrameMethod.Name; + } + + /// + /// Use this to get the Type from the CallStack. + /// This allows the calling method to know which type called it. + /// + /// Use this to add/substract from the base stack level you want to retrieve + /// Returns the Type as specified by the CallStack + public static Type GetTypeFromStack(int nPlusMinus = 0) + { + StackTrace stackTrace = new StackTrace(); + StackFrame stackFrame; + MethodBase stackFrameMethod; + + stackFrame = stackTrace.GetFrame(DEFAULT_STACK_FRAME_COUNT + nPlusMinus); + stackFrameMethod = stackFrame.GetMethod(); + return stackFrameMethod.ReflectedType; + } + + } +} diff --git a/Other/StateM.cs b/Other/StateM.cs new file mode 100644 index 0000000..b687ba3 --- /dev/null +++ b/Other/StateM.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections.Specialized; +using Yaulw.Tools; + +namespace Yaulw.Other +{ + /// + /// Easy StateManager to store and retrieve various Application States using an Enum + /// + /// + /// internal enum State + /// { + /// App_Started_bool, + /// App_ErrorsOccured_bool, + /// SpecialMode_CommandLine_Mode_bool, + /// } + /// + /// internal static StateM AppState = new StateM(typeof(State)); + /// + /// + /// + public class StateM : IDisposable + { + #region Private Members + + // Private Parsing Enums + private Type _StateKeys = null; + private StringDictionary _sdCurrentState = new StringDictionary(); // For State String Convertible Types + private Dictionary _sdCurrentStateObj = new Dictionary(); // For State Object Types + private bool _disposed = false; + private object _lock = new object(); + + #endregion + + #region Construction + + /// + /// The StateM needs to know what Keys to Get/Set + /// + /// Pass an Enum used to determine StateKeys + public StateM(Type Enum_StateKeys) + { + if (Enum_StateKeys.IsEnum) + { + _StateKeys = Enum_StateKeys; + } + else + throw new ArgumentException("StateKeys must be an Enum"); + } + + /// + /// Finalizer + /// + ~StateM() + { + Dispose(true); + } + + #endregion + + #region Public Properties + + /// + /// True if User Has passed in State Values + /// + public bool HasStates { get { return (_sdCurrentState.Count >= 1 || _sdCurrentStateObj.Count >= 1); } } + + #endregion + + #region Public Methods + + /// + /// Main Entry Function to Retrieve an State Value + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// State Value you want to retrieve + /// Default value to use if nothing was retrieved * Error occured * + /// value or default value, if not found + public T GetStateValue(Enum stateKey, T DefaultValue) + { + lock (_lock) + { + T RetVal = DefaultValue; + if (ObjTool.IsOfTypeConvertibleToString(DefaultValue)) + { + string Value = String.Empty; + if (GetStateValue(stateKey, out Value) && !String.IsNullOrEmpty(Value)) + RetVal = ObjTool.ConvertStringToObj(Value); + } + else + { + object o = null; + if (GetStateValue(stateKey, out o) && (o != null)) + RetVal = (T)o; + } + return RetVal; + } + } + + /// + /// Main Entry Function to Set a State Value + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// State Value you want to set + /// Value you want to set + /// true if successful, false otherwise + public bool SetStateValue(Enum stateKey, T Value) + { + lock (_lock) + { + bool bSuccess = false; + if (ObjTool.IsOfTypeConvertibleToString(Value)) + { + string strValue = ObjTool.ConvertObjToString(Value); + bSuccess = SetStateValue(stateKey, strValue); + } + else + { + bSuccess = SetStateValue(stateKey, (object)Value); + } + return bSuccess; + } + } + + #endregion + + #region Private Helper Methods + + /// + /// Private GetStateValue Getter Function + /// + /// pass in the State to look for + /// Returns the value or String.Empty, if none + /// true if the State Value Exists, false otherwise + private bool GetStateValue(Enum stateKey, out string Value) + { + lock (_lock) + { + Value = String.Empty; + if (_sdCurrentState.ContainsKey(stateKey.ToString())) + { + Value = _sdCurrentState[stateKey.ToString()]; + return true; + } + return false; + } + } + + /// + /// Private GetStateValue Getter Function + /// + /// pass in the State to look for + /// Returns the object or null, if noone + /// true if the State Value Exists, false otherwise + private bool GetStateValue(Enum stateKey, out object o) + { + lock (_lock) + { + o = null; + if (_sdCurrentStateObj.ContainsKey(stateKey.ToString())) + { + o = _sdCurrentStateObj[stateKey.ToString()]; + return true; + } + return false; + } + } + + /// + /// Private SetStateValue Setter Function + /// + /// pass in the State to Set + /// The Value to Set + /// true if the State Value was set, false otherwise + private bool SetStateValue(Enum stateKey, string Value) + { + lock (_lock) + { + try + { + _sdCurrentState[stateKey.ToString()] = Value; + return true; + } + catch (Exception) { /* ignore */ } + return false; + } + } + + /// + /// Private SetStateValue Setter Function + /// + /// pass in the State to Set + /// The Object to Set + /// true if the State Value was set, false otherwise + private bool SetStateValue(Enum stateKey, object o) + { + lock (_lock) + { + try + { + _sdCurrentStateObj[stateKey.ToString()] = o; + return true; + } + catch (Exception) { /* ignore */ } + return false; + } + } + + #endregion + + #region IDisposable Members + + /// + /// Dispose the State Object + /// + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer + GC.SuppressFinalize(this); + } + + /// + /// Dispose the State Object + /// + /// true, if called from within + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + foreach (object o in _sdCurrentStateObj) + { + if (o is IDisposable) + ((IDisposable)o).Dispose(); + } + _sdCurrentStateObj.Clear(); + } + + // Indicate that the instance has been disposed. + _sdCurrentState = null; + _sdCurrentStateObj = null; + _disposed = true; + } + } + + #endregion + } +} diff --git a/Other/TraceM.cs b/Other/TraceM.cs new file mode 100644 index 0000000..5aca81a --- /dev/null +++ b/Other/TraceM.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Threading = System.Threading; +using System.Diagnostics; + +namespace Yaulw.Other +{ + /// + /// Wrapper Class arround Tracing, designed to measure performance. + /// Use this class to measure performance arround your calls. + /// + /// TraceM.TraceBegin() + /// ... Some Code.... + /// ... Some More Code... + /// TraceM.TraceEnd("Custom Message"); + /// + /// + public static class TraceM + { + #region Private Statics + + private static Dictionary> s_ThreadTraceBeginTSMap = new Dictionary>(); + public static bool EnableTracing { get; set; } + + #endregion + + #region Construction + + /// + /// Construction * Tracing by default is disabled * + /// + static TraceM() + { + EnableTracing = false; + } + + #endregion + + #region Public Statics + + /// + /// Call this to Start the Performance Trace + /// + public static void TraceBegin() + { + if (EnableTracing) + { + uint curThreadId = (uint)Threading.Thread.CurrentThread.ManagedThreadId; + if (!s_ThreadTraceBeginTSMap.ContainsKey(curThreadId)) + s_ThreadTraceBeginTSMap[curThreadId] = new Stack(); + + s_ThreadTraceBeginTSMap[curThreadId].Push(DateTime.Now); + } + } + + /// + /// Us this to End the Performance Trace + /// + /// Custom Message you want displayed in the Trace Window + public static void TraceEnd(string CustomMessage = "") + { + if (EnableTracing) + { + uint curThreadId = (uint)Threading.Thread.CurrentThread.ManagedThreadId; + if (s_ThreadTraceBeginTSMap.ContainsKey(curThreadId)) + { + DateTime orgTime = s_ThreadTraceBeginTSMap[curThreadId].Pop(); + TimeSpan ts = DateTime.Now - orgTime; + Trace.WriteLine((CustomMessage + " (Time Taken in ms " + ts.TotalMilliseconds.ToString() + ")")); + } + } + } + + #endregion + } +} diff --git a/Other/Versioning.cs b/Other/Versioning.cs new file mode 100644 index 0000000..d95fcea --- /dev/null +++ b/Other/Versioning.cs @@ -0,0 +1,550 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Diagnostics; + +namespace Yaulw.Other +{ + /// + /// Interface of Versioning, to check if the passed in version is supported by the Versioning Object + /// + public interface IVersionSupported + { + bool IsSupported(string Version); + bool IsSupported(Versioning Version); + bool IsSupported(Version Version); + } + + /// + /// Allows us to easily wrap Versioning Functionallity, + /// into Objects. Similar to Assembly.Version Class, however, + /// it allows the * character. This character specifies all and + /// allows for wildchar versioning schemes useful for certain + /// scenarios via IVersionSupported. + /// --------------------------------------------------- + /// [Major version].[Minor version].[Build number].[Revision number] + /// --------------------------------------------------- + /// Major version can be any valid uint as well as * + /// Minor version can be any valid uint as well as * + /// Build number can be any valid uint as well as * + /// Revision number can be any valid uint as well as * + /// + public class Versioning : ICloneable, IComparable, IVersionSupported + { + #region Public consts + + /// + /// Int value indicating a * (wildcard) character + /// + public const int STAR_ALL = -1; + + #endregion + + #region Private Members + + private int _MajorVersion = STAR_ALL; + private int _MinorVersion = STAR_ALL; + private int _BuildNumber = STAR_ALL; + private int _RevisionNumber = STAR_ALL; + + #endregion + + #region Construction + + /// + /// Use this to initialize the class with a version string, can not contain multiple versions seperated by ";" + /// + /// any version string in the format [Major].[Minor].[Build].[Revision], like 5.3.3.1 or 5.4.*.* + /// if Major,Minor,Build, or Revision < -1 + public Versioning(string strVersion) + { + if (!IsValidVersionStr(strVersion) || strVersion.Contains(";")) + throw new ArgumentException("Invalid Version String"); + ParseVersion(strVersion, out this._MajorVersion, out this._MinorVersion, out this._BuildNumber, out this._RevisionNumber); + } + + /// + /// Initialize Versioning with a single MajorVersion and MinorVersion. + /// + /// Major Version + /// Minor Version + /// if Major,Minor,Build, or Revision < -1 + public Versioning(int MajorVersion, int MinorVersion) + { + if (MajorVersion >= STAR_ALL && MinorVersion >= STAR_ALL) + { + _MajorVersion = MajorVersion; + _MinorVersion = MinorVersion; + } + else + throw new ArgumentException("MajorVersion or MinorVersion is invalid"); + } + + /// + /// Initialize Versioning with a single MajorVersion,MinorVersion, and BuildNumber. + /// + /// Major Version + /// Minor Version + /// Build Number + /// if Major,Minor,Build, or Revision < -1 + public Versioning(int MajorVersion, int MinorVersion, int BuildNumber) + { + if (MajorVersion >= STAR_ALL && MinorVersion >= STAR_ALL && _BuildNumber >= STAR_ALL) + { + _MajorVersion = MajorVersion; + _MinorVersion = MinorVersion; + _BuildNumber = BuildNumber; + } + else + throw new ArgumentException("MajorVersion,MinorVersion, or BuildNumber is invalid"); + } + + /// + /// Initialize Versioning with a single MajorVersion,MinorVersion,BuildNumber and RevisionNumber. + /// + /// Major Version + /// Minor Version + /// Build Number + /// Revision Number + /// if Major,Minor,Build, or Revision < -1 + public Versioning(int MajorVersion, int MinorVersion, int BuildNumber, int RevisionNumber) + { + if (MajorVersion >= STAR_ALL && MinorVersion >= STAR_ALL && _BuildNumber >= STAR_ALL && RevisionNumber >= STAR_ALL) + { + _MajorVersion = MajorVersion; + _MinorVersion = MinorVersion; + _BuildNumber = BuildNumber; + _RevisionNumber = RevisionNumber; + } + else + throw new ArgumentException("MajorVersion,MinorVersion, BuildNumber, or RevisionNumber is invalid"); + + } + + /// + /// Initialize Versioning with a .Net Version Object + /// + /// a .Net Version Object + public Versioning(Version version) + { + _MajorVersion = version.Major; + _MinorVersion = version.Minor; + _BuildNumber = version.Build; + _RevisionNumber = version.Revision; + } + + #endregion + + #region Versioning Getter/Setter + + /// + /// Set/Retrieve Version + /// + public String Version + { + get + { + return VersionToVersionString(_MajorVersion, _MinorVersion, _BuildNumber, _RevisionNumber); + } + set + { + if (IsValidVersionStr(value) && !value.Contains(";")) + ParseVersion(value, out this._MajorVersion, out this._MinorVersion, out this._BuildNumber, out this._RevisionNumber); + else + throw new ArgumentException("Invalid Version String"); + } + } + + #endregion + + #region Public Static Helpers + + /// + /// Gets a Versioning object for the Version Information of a File + /// + /// Full File Name and Path + /// returns a new Versioning Object for a File, or null if error occured + public static Versioning GetFileVersioning(string FileNameNPath) + { + if (!string.IsNullOrEmpty(FileNameNPath) && System.IO.File.Exists(FileNameNPath)) + { + try + { + FileVersionInfo info = FileVersionInfo.GetVersionInfo(FileNameNPath); + return new Versioning(info.FileMajorPart, info.FileMinorPart, info.FileBuildPart, info.FilePrivatePart); + } + catch (Exception) { /* ignore */ } + } + return null; + } + + /// + /// Parses out a single Version from a string + /// + /// pass in a Version format [Major].[Minor].[Build], like 5.3.3 or 5.4.* + /// returns an int or STAR_ALL + /// returns an int or STAR_ALL + /// returns an int or STAR_ALL + /// returns an int or STAR_ALL + public static void ParseVersion(string strVersion, out int MajorVersion, out int MinorVersion, out int BuildNumber, out int RevisionNumber) + { + if (!IsValidVersionStr(strVersion) || strVersion.Contains(";")) + throw new ArgumentException("Invalid Version String"); + + MajorVersion = STAR_ALL; + MinorVersion = STAR_ALL; + BuildNumber = STAR_ALL; + RevisionNumber = STAR_ALL; + string[] MajorMinorPossBuildVersion = strVersion.Split('.'); + try + { + for (int i = 0; i < MajorMinorPossBuildVersion.Length; ++i) + { + if (i == 0) + { + if (MajorMinorPossBuildVersion[0] != "*") + MajorVersion = int.Parse(MajorMinorPossBuildVersion[0]); + } + else if (i == 1) + { + if (MajorMinorPossBuildVersion[1] != "*") + MinorVersion = int.Parse(MajorMinorPossBuildVersion[1]); + } + else if (i == 2) + { + if (MajorMinorPossBuildVersion[2] != "*") + BuildNumber = int.Parse(MajorMinorPossBuildVersion[2]); + } + else if (i == 3) + { + if (MajorMinorPossBuildVersion[2] != "*") + RevisionNumber = int.Parse(MajorMinorPossBuildVersion[3]); + } + } + + } + catch (Exception) { /* Ignore */ } + } + + /// + /// Turns Version Numbers into a Version String + /// + /// Major Version + /// Minor Version, can be * + /// Build Number, can be * or UNINITIALIZED + /// the Version String + public static string VersionToVersionString(int MajorVersion, int MinorVersion = STAR_ALL, int BuildNumber = STAR_ALL, int RevisionNumber = STAR_ALL) + { + string strRetVal = String.Empty; + if (MajorVersion >= STAR_ALL) + { + // Major Version + if (MajorVersion == STAR_ALL) + strRetVal += "*"; + else + strRetVal += MajorVersion.ToString(); + + // Minor Version + if (MinorVersion >= STAR_ALL) + { + strRetVal += "."; + if (MinorVersion == STAR_ALL) + strRetVal += "*"; + else + strRetVal += MinorVersion.ToString(); + } + + // Build Number + if (BuildNumber >= STAR_ALL) + { + strRetVal += "."; + if (BuildNumber == STAR_ALL) + strRetVal += "*"; + else + strRetVal += BuildNumber.ToString(); + } + + // Revision Number + if (RevisionNumber >= STAR_ALL) + { + strRetVal += "."; + if (RevisionNumber == STAR_ALL) + strRetVal += "*"; + else + strRetVal += RevisionNumber.ToString(); + } + } + return strRetVal; + } + + #endregion + + #region Private Stats - Validation + + private const string CharSet_AllowedNumeric = "0123456789"; + private const string CharSet_AllowedVersionChars = CharSet_AllowedNumeric + "*;."; + + /// + /// Generic Function to use with Allowed Character Sets above + /// + /// string to evaluate + /// Pass in one of the legal character consts declared above + /// true if valid, false otherwise + private static bool ContainsOnlyLegalChars(string TextToEvaluate, string TextToEvaluateWith) + { + foreach (char c in TextToEvaluate.ToCharArray()) + { + bool bFound = (TextToEvaluateWith.IndexOf(c) >= 0); + if (!bFound) + return false; + } + return true; + } + + /// + /// Used to Validate a Version string of format 12.1.12;12.2.*;12.2.1 + /// + /// a Version String + /// true if valid, false otherwise + public static bool IsValidVersionStr(string VersionStr) + { + if (String.IsNullOrEmpty(VersionStr)) + { + return false; + } + else if (VersionStr.Length < 1 && VersionStr.Length > 14) // restrict Version String Length + { + return false; + } + else if (!ContainsOnlyLegalChars(VersionStr, CharSet_AllowedVersionChars)) + { + return false; + } + else + { + return true; + } + } + + #endregion + + #region ICloneable Members + + /// + /// Clone the Object + /// + /// a copy of this Versioning Object + public object Clone() + { + Versioning versioning = new Versioning(this._MajorVersion, this._MinorVersion, this._BuildNumber, this._RevisionNumber); + return versioning; + } + + #endregion + + #region IComparable Members + + /// + /// Compare two Versioning Objects + /// + /// a Versioning Object + /// -1, 0, +1 + public int CompareTo(object obj) + { + if (obj is Versioning) + { + Versioning v = (Versioning)obj; + //*.*.* + //*.2.* + //1.*.* + //1.1.* + //1.1.1 + //2.2.2 + //2.2.* + int nCompare = 0; + nCompare = this._MajorVersion.CompareTo(v._MajorVersion); + if(nCompare == 0) + nCompare = this._MinorVersion.CompareTo(v._MinorVersion); + if(nCompare == 0) + nCompare = this._BuildNumber.CompareTo(v._BuildNumber); + if (nCompare == 0) + nCompare = this._RevisionNumber.CompareTo(v._BuildNumber); + return nCompare; + } + else + { + throw new ArgumentException("object is not a Versioning"); + } + } + + #endregion + + #region IVersionSupported Members + + /// + /// Returns true if the Version String passed in is supported/matches Versioning for this object + /// + /// a Versioning string to validate against + /// true if supported, false otherwise + public bool IsSupported(string Version) + { + Versioning version = new Versioning(Version); + return IsSupported(version); + } + + /// + /// Returns true if the Version passed in is supported/matches Versioning for this object + /// + /// a Versioning Object to validate against + /// true if supported, false otherwise + public bool IsSupported(Versioning Version) + { + if (Version != null) + { + int nCompare = this.CompareTo(Version); + if (nCompare == 0) + { + return true; + } + else if (nCompare == 1) + { + return false; + } + else if (nCompare == -1) + { + if (((this._MajorVersion == STAR_ALL) || (this._MajorVersion == Version._MajorVersion)) && + ((this._MinorVersion == STAR_ALL) || (this._MinorVersion == Version._MinorVersion)) && + ((this._BuildNumber == STAR_ALL) || (this._BuildNumber == Version._BuildNumber)) && + ((this._RevisionNumber == STAR_ALL) || (this._RevisionNumber == Version._RevisionNumber)) + ) + { + return true; + } + else + { + return false; + } + } + } + return false; + } + + /// + /// Returns true if the Version passed in is supported/matches Versioning for this object + /// + /// a Version Object to Validate against + /// true if supported, false otherwise + public bool IsSupported(Version Version) + { + Versioning version = new Versioning(Version.Major, Version.Minor, Version.Build, Version.Revision); + return true; + } + + #endregion + } + + /// + /// Allows us to easily parse/sort/search multiple versioning classes + /// + public static class Versionings + { + /// + /// Used to parse multiple ";" seperated versions from a string + /// + /// single/multiple version strings, seperated by ";", in the format [Major].[Minor].[Build].[Revision], like 5.3.3.1 or 5.4.*.* + /// a sorted array of versionings or null if error occured + public static Versioning[] ParseVersions(string strVersions) + { + if (!Versioning.IsValidVersionStr(strVersions)) + return null; + + List RetValueVersions = new List(); + string[] Versions = strVersions.Split(';'); + foreach (string Version in Versions) + RetValueVersions.Add(new Versioning(Version)); + + RetValueVersions.Sort(); + return RetValueVersions.ToArray(); + } + + #region IsSupportedFile + + /// + /// Use this to find out if a File Version is supported by the passed in Versions string + /// + /// single/multiple version strings, seperated by ";", in the format [Major].[Minor].[Build], like 5.3.3 or 5.4.* + /// Full File Name and Path + /// true if the File Version is supported by the Versions string + public static bool IsSupportedFile(string strVersions, string FileNameNPath) + { + return IsSupported(ParseVersions(strVersions), Versioning.GetFileVersioning(FileNameNPath)); + } + + /// + /// Use this to find out if a File Version is supported by the passed in Versions [] + /// + /// single/multiple versioning objects + /// Full File Name and Path + /// true if the File Version is supported by the Versions string + public static bool IsSupportedFile(Versioning[] Versions, string FileNameNPath) + { + return IsSupported(Versions, Versioning.GetFileVersioning(FileNameNPath)); + } + + #endregion + + #region IsSupported + + /// + /// Pass in a Versions string, and a Version to check against. Returns true, if the Version is IVersionSupported by + /// the passed in Versions string. + /// + /// single/multiple version strings, seperated by ";", in the format [Major].[Minor].[Build].[Revision], like 5.3.3 or 5.4.* + /// any version string in the format [Major].[Minor].[Build], like 5.3.3 or 5.4.* + /// true, if a Versioning Object in the Versions returns true for IVersionSupported, false otherwise + public static bool IsSupported(string strVersions, string strVersion) + { + return IsSupported(ParseVersions(strVersions), new Versioning(strVersion)); + } + + /// + /// Pass in a single/multipe Versions object, and a Version object to check against. Returns true, if the Version is IVersionSupported by + /// the passed in Versions Objects. + /// + /// single/multiple versioning objects + /// single versioning object + /// true, if a Versioning Object in the Versions returns true for IVersionSupported, false otherwise + public static bool IsSupported(Versioning[] Versions, Versioning Version) + { + // Let IVersionSupported do all the work + foreach (Versioning _version in Versions) + { + if (_version.IsSupported(Version)) + return true; + } + return false; + } + + /// + /// Pass in a single/multipe Versions object, and a Version object to check against. Returns true, if the Version is IVersionSupported by + /// the passed in Versions Objects. + /// + /// single/multiple versioning objects + /// single version object + /// true, if a Versioning Object in the Versions returns true for IVersionSupported, false otherwise + public static bool IsSupported(Versioning[] Versions, Version Version) + { + // Let IVersionSupported do all the work + foreach (Versioning _version in Versions) + { + if (_version.IsSupported(Version)) + return true; + } + return false; + } + + #endregion + } +} + diff --git a/Process/PStartInfo.cs b/Process/PStartInfo.cs new file mode 100644 index 0000000..3ed3aa8 --- /dev/null +++ b/Process/PStartInfo.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Diagnostics; +using System.IO; +using Yaulw.Tools; + +namespace Yaulw.Process +{ + /// + /// Helper Class to create ProcessStartInfo Objects + /// + public static class PStartInfo + { + /// + /// Creates a simple ProcessStartInfo Object + /// + /// Exe File and Path to execute + /// CmdLine Params to process, optional + /// If true, will set "RunAs" For the Process, to Run as Administrator + /// the windows style to initialize main window with + /// True to use Explorer.exe shellexecute, false otherise + /// a ProcessStartInfo Object or null if error occured + public static ProcessStartInfo CreateProcess(string ExeFileNPath, string CmdLine = "", string WorkingDir = "", bool bUseRunAs = false, ProcessWindowStyle WindowStyle = ProcessWindowStyle.Normal, bool bUseShellExecute = true) + { + if (!String.IsNullOrEmpty(ExeFileNPath) && System.IO.File.Exists(ExeFileNPath)) + { + ProcessStartInfo startInfo = new ProcessStartInfo(ExeFileNPath, CmdLine); + startInfo.WindowStyle = WindowStyle; + startInfo.UseShellExecute = bUseShellExecute; + if (bUseRunAs) + startInfo.Verb = "runas"; + + // Set up Working Directory, if one is found + if(!String.IsNullOrEmpty(WorkingDir)) + startInfo.WorkingDirectory = PathNaming.PathEndsWithSlash(WorkingDir); + else + startInfo.WorkingDirectory = Path.GetDirectoryName(ExeFileNPath); + return startInfo; + } + return null; + } + + /// + /// Creates an url ProcessStartInfo Object + /// + /// url to launche + /// a ProcessStartInfo Object or null if error occured + public static ProcessStartInfo LaunchUrl(string url) + { + if (!String.IsNullOrEmpty(url)) + { + ProcessStartInfo startInfo = new ProcessStartInfo(url); + startInfo.UseShellExecute = true; + return startInfo; + } + return null; + } + + /// + /// Creates a CMD.Exe CommandLine Executable ProcessStartInfo Object + /// + /// Full FileName and Path to script file to execute via CMD.exe + /// If true, will set "RunAs" For the Process, to Run as Administrator + /// a ProcessStartInfo Object or null if error occured + public static ProcessStartInfo CreateCMDScriptProcess(string scriptFileNPath, bool bUseRunAs = false) + { + if (!String.IsNullOrEmpty(scriptFileNPath) && System.IO.File.Exists(scriptFileNPath)) + { + //The "/C" Tells Windows to Run The Command then Terminate + string strCmdLine = "/C " + '\"' + scriptFileNPath + '\"'; + string WindowsSystem32Folder = System.Environment.GetFolderPath(Environment.SpecialFolder.System); + ProcessStartInfo startInfo = new ProcessStartInfo((WindowsSystem32Folder + "\\" + "CMD.exe"), strCmdLine); + //startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; + startInfo.CreateNoWindow = true; + startInfo.UseShellExecute = false; + if (bUseRunAs) + startInfo.Verb = "runas"; + startInfo.WorkingDirectory = Path.GetDirectoryName(scriptFileNPath); + return startInfo; + } + return null; + } + + /// + /// Creates a CMD.Exe CommandLine Executable ProcessStartInfo Object + /// + /// Dos Command to Execute + /// If true, will set "RunAs" For the Process, to Run as Administrator + /// a ProcessStartInfo Object or null if error occured + public static ProcessStartInfo CreateCMDDosCommandProcess(string DosCommand, bool bUseRunAs = false) + { + if (!String.IsNullOrEmpty(DosCommand)) + { + //The "/C" Tells Windows to Run The Command then Terminate + string strCmdLine = "/C " + '\"' + DosCommand + '\"'; + string WindowsSystem32Folder = System.Environment.GetFolderPath(Environment.SpecialFolder.System); + ProcessStartInfo startInfo = new ProcessStartInfo((WindowsSystem32Folder + "\\" + "CMD.exe"), strCmdLine); + //startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; + startInfo.CreateNoWindow = true; + startInfo.UseShellExecute = false; + if (bUseRunAs) + startInfo.Verb = "runas"; + return startInfo; + } + return null; + } + } +} diff --git a/Process/PStarter.cs b/Process/PStarter.cs new file mode 100644 index 0000000..0ad258a --- /dev/null +++ b/Process/PStarter.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Diagnostics; +using System.IO; + +namespace Yaulw.Process +{ + /// + /// Used to Manage Launching/Killing Processes In an Application + /// + public static class PStarter + { + #region Private Static Members + private static Dictionary _StartedProcesses = new Dictionary(); + #endregion + + #region Public Process Starter Methods + + /// + /// Use this to call a DosCommand in a hidden command prompt and have the command return + /// you a value back, read from standard output + /// + /// processStartInfo + /// true to wait till process ends, false otherwise + /// the PID of the the newly started Process, or 0 if an error occured + public static string RunDosCommand(string DosCommand) + { + ProcessStartInfo processStart = PStartInfo.CreateCMDDosCommandProcess(DosCommand, true); + processStart.RedirectStandardOutput = true; + string output = String.Empty; + if ((processStart != null) && System.IO.File.Exists(processStart.FileName)) + { + // Start the Process + try + { + System.Diagnostics.Process p1; + p1 = System.Diagnostics.Process.Start(processStart); + output = p1.StandardOutput.ReadToEnd(); + p1.WaitForExit(); + if (String.IsNullOrEmpty(output)) + output = p1.StandardOutput.ReadToEnd(); + } + catch (Exception e) { string msg = e.Message; /* ignore */ } + } + return output; + } + + + /// + /// Always Starts a New Process + /// + /// processStartInfo + /// true to wait till process ends, false otherwise + /// true to check if the process of the same name has already been started, false otherwise + /// the PID of the the newly started Process, or 0 if an error occured + //public static uint StartProcess(ProcessStartInfo processStart, bool bWait = false, bool bMustBeUnique = false) + public static uint StartProcess(ProcessStartInfo processStart, bool bWait, bool bMustBeUnique) + { + if ((processStart != null) && System.IO.File.Exists(processStart.FileName)) + { + // Enforce Uniqueness * DISABLED * We can't ENFORCE UNIQUENESS WITHOUT ALSO INCLUDING COMMAND-LINE PARAMETERS + // ~TO DO SOMETIME LATER * DOESN"T MATTER HERE * ALREADY HANDLED BY CALLER ANYWAY * + //if (bMustBeUnique && ContainsProcessExe(processStart.FileName)) + //{ + // return 0; + //} + //else if (bMustBeUnique) + //{ + // // Iterate the system's Processes's and make sure that the same process doesn't exist + // System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName(Path.GetFileNameWithoutExtension(processStart.FileName)); + // foreach (System.Diagnostics.Process process in processes) + // { + // // Found, return 0 + // if (String.Compare(process.MainModule.FileName, processStart.FileName, true) == 0) + // return 0; + // } + //} + + // Start the Process + try + { + System.Diagnostics.Process p1; + p1 = System.Diagnostics.Process.Start(processStart); + + // DISABLED + // Map if for later use + //_StartedProcesses.Add((uint)p1.Id, processStart.FileName.ToLower()); + + // Wait if asked + if (bWait) + p1.WaitForExit(); + + return (uint)p1.Id; + } + catch (Exception) { /* ignore */ } + } + return 0; + } + + /// + /// Kills any process started thru PStarter, if it matches the Passed in PID + /// + /// The Pid of the Process to Stop + /// true to only shutdown a process that was started by PStarted + /// The time in Seconds to wait after closing the Process + /// true to try sending WM_CLOSE message to Window prior Killing Process + /// The time in Seconds to wait after sending WM_CLOSE message for Process to Close * Only takes effect if bTryClosingMainWindowFirst is true * + /// true if process stopped, false otherwise + //public static bool KillProcess(uint PID, bool bMustBeHaveBeenStartedByUs = false, uint CloseWaitTimeInSeconds = 1, bool bTryClosingMainWindowFirst = true, uint MainWindowCloseWaitTimeInSeconds = 2) + public static bool KillProcess(uint PID, bool bMustBeHaveBeenStartedByUs, uint CloseWaitTimeInSeconds, bool bTryClosingMainWindowFirst, uint MainWindowCloseWaitTimeInSeconds) + { + + // Valid Process Id + bool bValidToClose = (PID != 0); + + // Make sure that PStarted Started This Process + if (bValidToClose && bMustBeHaveBeenStartedByUs) + bValidToClose = _StartedProcesses.ContainsKey(PID); + + // Is this process valid to close? + if (bValidToClose) + { + System.Diagnostics.Process p1 = null; + // Try getting the Process + try { p1 = System.Diagnostics.Process.GetProcessById((int)PID); } + catch (Exception) {/* ignore */} + + // Process Found + if (p1 != null) + { + // If Main Window Exists, Try Closing this first + if (bTryClosingMainWindowFirst) + { + IntPtr hWnd = p1.MainWindowHandle; + if (hWnd == IntPtr.Zero) + { + // Try getting the window handle by Iterating TopLevel Windows + hWnd = Win32.Functions.GetFirstTopLevelWindowForProcess((int)PID); + } + + // If we have a valid Window Handle, try closing it nicely + if (hWnd != IntPtr.Zero && Win32.User32.IsWindow(hWnd)) + { + // Try the new school way && the old school way !!! + p1.CloseMainWindow(); + Win32.User32.PostMessage(hWnd, (int)Win32.Definitions.WM.WM_CLOSE, IntPtr.Zero, IntPtr.Zero); + + // Wait a little.. as specified by the configuration + System.Threading.Thread.Sleep((int)TimeSpan.FromSeconds(MainWindowCloseWaitTimeInSeconds).TotalMilliseconds); + + // Retry getting the Process *Should be closed by now* + try { p1 = null; p1 = System.Diagnostics.Process.GetProcessById((int)PID); } + catch (Exception) {/* ignore */} + } + } + + // Last Resort, Always try to Kill the Process + try { if (p1 != null) p1.Kill(); } + catch (Exception) { /* ignore */ } + + // Wait a little + System.Threading.Thread.Sleep((int)TimeSpan.FromSeconds(CloseWaitTimeInSeconds).TotalMilliseconds); + + // Retry getting the Process *Must be closed by now* + try { p1 = null; p1 = System.Diagnostics.Process.GetProcessById((int)PID); } + catch (Exception) {/* ignore */} + if (p1 == null) + { + // DISABLED + //_StartedProcesses.Remove(PID); + return true; + } + } + } + return false; + } + + #endregion + + #region Public Process Starter Helper Methods + + /// + /// Retrieves the PIDs that were started using PStarter + /// + /// an uint[] array with Pids, or an empty array if none found + public static uint[] AllStartedProcessIDs() + { + // DISABLED + //List Pids = new List(); + //foreach (int key in _StartedProcesses.Keys) + // Pids.Add((uint)key); + //return Pids.ToArray(); + return null; + } + + /// + /// Since when we start a Process and it can die/killed externally we wouldn't know, + /// PIDs #'s could be re-used by the OS, so we could accidentially kill a Process that isn't our own. + /// This functions allows a monitoring program to call in with currently running PIDs they know are good + /// on the running system, we can use these numbers and discard any PIDs internally that are no longer accurate + /// * This function will DELETE PIDs/Keys internally * call it only with is a complete list of all PIDs. + /// + /// an uint[] array with currently Running PIDs, that we can compare to our started PIDs + public static void PerformGeneralCleanupOfAllStartedProcessIDs(uint[] currentlyRunningPIDs) + { + // DISABLED + //if ((currentlyRunningPIDs != null) && (currentlyRunningPIDs.Length > 0)) + //{ + // // Iterate thru the Internal DS and track any PID Keys that are no longer valid + // List RunningPidKeysToRemove = new List(); + // foreach (uint key in _StartedProcesses.Keys) + // { + // if (!currentlyRunningPIDs.Contains(key)) + // RunningPidKeysToRemove.Add(key); + // } + + // // Remove * no longer valid * PID / Keys + // foreach (uint keyToRemove in RunningPidKeysToRemove) + // _StartedProcesses.Remove(keyToRemove); + //} + } + + /// + /// Returns true if the ProcessExe was started with PStarter, False otherwise + /// + /// Process Executable Filename and Path + /// true, if found, false otherwise + public static bool ContainsProcessExe(string FileName) + { + return _StartedProcesses.ContainsValue(FileName.ToLower()); + } + + /// + /// Get's the first PID that matches the Given Exe Process FileName + /// + /// Process Executable Filename and Path + /// a Valid PID or 0 if not found + public static uint GetFirstPIDForProcessExe(string FileName) + { + if (ContainsProcessExe(FileName)) + { + foreach (uint key in _StartedProcesses.Keys) + { + if (String.Compare(_StartedProcesses[key], FileName, true) == 0) + return key; + } + } + return 0; + } + + /// + /// Get's all the PIDs that matches the Given Exe Process FileName + /// + /// Process Executable Filename and Path + /// valid PIDs or empty PID array, if not found + public static uint[] GetPIDsForProcessExe(string FileName) + { + List foundProcess = new List(); + if (ContainsProcessExe(FileName)) + { + foreach (uint key in _StartedProcesses.Keys) + { + if (String.Compare(_StartedProcesses[key], FileName, true) == 0) + foundProcess.Add(key); + } + } + return foundProcess.ToArray(); + } + + #endregion + } +} diff --git a/Process/ProcessW.cs b/Process/ProcessW.cs new file mode 100644 index 0000000..c9fbdb6 --- /dev/null +++ b/Process/ProcessW.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Diag = System.Diagnostics; +using System.Management; + +namespace Yaulw.Process +{ + /// + /// Wrapper Class around System.Diagnostics's Process Class. + /// Helper functions to make sure that we can safely interact with Processes. + /// + public static class ProcessW + { + #region Process Caching + + /// + /// Process PID Lookup Cache - so that we can cache PIDs, we don't make a new system call + /// Use this Property to Cache Process PIDs Lookups + /// + public static List ProcessPidsRunningCache { get { return s_RunningProcessesPids; } } + private static List s_RunningProcessesPids = new List(); + + /// + /// Process Obj Lookup Cache - so that for processes with the same name, we don't make a new system call + /// Use this Property to Cache Process Obj Lookups + /// + public static Dictionary> ProcessObjsGetterCache { get { return s_processGetterCache; } } + private static Dictionary> s_processGetterCache = new Dictionary>(); + + /// + /// Clears the Process Obj Lookup Cache - Used in conjuction with AllRunningProcessesOf() + /// + public static void ClearProcessObjsGetterCache() { s_processGetterCache.Clear(); } + + #endregion + + #region Query For Processes + + /// + /// Safe GetProcessById Function, to make sure that it returns a valid Process Object + /// + /// the PID to get the Process Object for + /// a valid Process obj, or null + /// true if successful, false otherwise + public static bool GetProcessById(int PID, out Diag.Process p) + { + p = null; + try + { + p = Diag.Process.GetProcessById(PID); + return (p != null); + } + catch (Exception) { /*ignore */ } + return false; + } + + /// + /// Quick Check to see if other Process of that Name Are running + /// + /// name of process + /// set to true to ignore Visual Studio's host .exe (debugger process) + /// true, if only one Process of that Name is running, false otherwise + public static bool IsTheOnlyProcessRunning(string ProcessName, bool bIgnoreVSHost = true) + { + if (!string.IsNullOrEmpty(ProcessName)) + { + // Visual Studio could be running this... + int nLength1 = Diag.Process.GetProcessesByName(ProcessName).Length; + int nLength2 = (bIgnoreVSHost)? 0 : Diag.Process.GetProcessesByName(ProcessName + ".vshost").Length; + int nProcessCount = nLength1 + nLength2; + return (nProcessCount == 0 || nProcessCount == 1); + } + return true; + } + + /// + /// Use this to quickly retrieve all Running PID on this machine + /// * Excellent performance 10 MS * + /// + /// returns all running Pids + public static List AllRunningPids() + { + Diag.Process[] processes = Diag.Process.GetProcesses(); + List allRunningPids = new List(); + if (allRunningPids != null) + { + foreach (Diag.Process p in processes) + allRunningPids.Add((uint)p.Id); + } + return allRunningPids; + } + + /// + /// Returns all Processes of that ProcessName + /// + /// name of process + /// If true, for .exe's in the Cache will return immediatly without a system call. Use ClearProcessObjsGetterCache() to clear Cache. + /// set to true to ignore Visual Studio's host .exe (debugger process) + /// Returns all processes that match the process name, or empty array, if none found + public static Diag.Process[] AllRunningProcessesOf(string ProcessName, bool bUseLookupCache = false, bool bIgnoreVSHost = true) + { + if (!string.IsNullOrEmpty(ProcessName)) + { + try + { + // Immediatly Return from Cache the Process Getter Calls * Performance Improvement * + if (bUseLookupCache && s_processGetterCache.ContainsKey(ProcessName)) + return s_processGetterCache[ProcessName].ToArray(); + + // If a lookup cache is used, we won't deal with the complexity of also dealing with VSHost, + // so ignore this setting + if (bIgnoreVSHost && bUseLookupCache) + bIgnoreVSHost = true; + + // Running Processes found + List runningProcess = new List(); + + // Regular Process Lookup... + Diag.Process[] processes = Diag.Process.GetProcessesByName(ProcessName); + if (processes != null && processes.Length > 0) + { + foreach (Diag.Process p in processes) + runningProcess.Add(p); + } + + // Visual Studio could be running this... + if (!bIgnoreVSHost) + { + processes = Diag.Process.GetProcessesByName(ProcessName + ".vshost"); + if (processes != null && processes.Length > 0) + { + foreach (Diag.Process p in processes) + runningProcess.Add(p); + } + } + + // Immediatly Cache the Process Getter Calls + if (bUseLookupCache && !s_processGetterCache.ContainsKey(ProcessName)) + { + s_processGetterCache[ProcessName] = new List(); + s_processGetterCache[ProcessName].AddRange(runningProcess); + } + + // Return the found Processes + return runningProcess.ToArray(); + } + catch (Exception) { /* ignore */ } + } + return new Diag.Process[0]{}; // return empty array + } + + #endregion + + #region Process Exe FileName N CommandLine Getters + + /// + /// Uses WMI to retrieve Process's FileNameNPath and the Command-Line Parameters for the specified Process + /// + /// a valid Pid to retrieve Command-line and Process FileName for + /// passing out full process exe file name and path for pid, if found + /// passing out CommandLine params for the Pid, if found + /// true, if the process was found and at least ProcessFileNameNPath was written out, false otherwise + public static bool GetCommandLineArgumentsForProcessPID(int ProcessId, out string ProcessFileNameNPath, out string CommandLineParams) + { + ProcessFileNameNPath = String.Empty; + CommandLineParams = String.Empty; + try + { + // Use WMI to retrieve the info we need (.net does not provide any other way, it appears) + string wmiQuery = string.Format("SELECT CommandLine from Win32_Process WHERE ProcessId = {0}", ProcessId); + ManagementObjectSearcher searcher = new ManagementObjectSearcher(wmiQuery); + ManagementObjectCollection retObjectCollection = searcher.Get(); + if ((retObjectCollection != null) && (retObjectCollection.Count > 0)) + { + foreach (ManagementObject retObject in retObjectCollection) // This will always be only 1 * retObjectCollection doesn't implement [] + { + if (retObject != null) // to be even more save... + { + string strLocal = String.Empty; + if (retObject["CommandLine"] != null && !String.IsNullOrEmpty(retObject["CommandLine"].ToString())) + strLocal = retObject["CommandLine"].ToString().Trim(); + + if (!String.IsNullOrEmpty(strLocal)) + { + // This should work + int firstQuotIndex = strLocal.IndexOf('\"'); + int SecondQuotIndex = (firstQuotIndex >= 0) ? strLocal.IndexOf('\"', firstQuotIndex + 1) : -1; + + // Pass out * if we at least have a Process FileNameNPath * + if (firstQuotIndex > -1 && SecondQuotIndex > -1) + { + ProcessFileNameNPath = Win32.Functions.GetLongFileNameNPathOrPath(strLocal.Substring(firstQuotIndex + 1, SecondQuotIndex - 1)); + CommandLineParams = (strLocal.Length > SecondQuotIndex + 2) ? strLocal.Substring(SecondQuotIndex + 2) : String.Empty; + return true; + } + } + } + } + } + } + catch (Exception) { /* ignore */ } + return false; + } + + /// + /// Usefull for making sure that the exact Process Image (including Command-Line Prms) is currently in a running state. + /// + /// Pass in a list of Process[] where to search for ProcessFileNameNPath and CommandLineParams + /// The exact Process FileName and Path (Image .exe) to find match for + /// The exact Command-Line Parameters to find match for + /// a PID hint * to speed up look * caller can pass in a PID that 'most likely' is the PID we are looking for (Can be zero) + /// If we found the process match, we'll pass out the process to the caller (runningProcesses[]), null otherwise + /// If we found the process match, we'll pass out the index to the caller (runningProcesses[]), -1 otherwise + /// True, if exact Match was found, False Otherwise + public static bool FoundExactProcessExeNCommandLinePrmsInRunningProcesses(Diag.Process[] runningProcesses, string ProcessFileNameNPath, string CommandLineParams, int PID_Hint, out Diag.Process process, out int indexFound) + { + process = null; + indexFound = -1; + try + { + if (runningProcesses == null || runningProcesses.Length == 0) + return false; + + if (PID_Hint < 0) + PID_Hint = 0; + + bool bFoundProcessRunning = false; + + // * For Performance Reasons * ProcessW.GetCommandLineArgumentsForProcessPID() takes a long time, + // Use PID_Hint first, to check if that PID is it + if (PID_Hint > 0) + { + for (int i = 0; i < runningProcesses.Length; ++i) + { + Diag.Process rProcess = runningProcesses[i]; + if (rProcess != null && (rProcess.Id == PID_Hint)) + { + string ProcessFileNameNPath_Local; + string CommandLineParams_Local; + if (ProcessW.GetCommandLineArgumentsForProcessPID(rProcess.Id, out ProcessFileNameNPath_Local, out CommandLineParams_Local)) + { + // See if we find a match + if (ProcessFileExeNCommandLineParamsComparer_FoundMatch(ProcessFileNameNPath_Local, CommandLineParams_Local, ProcessFileNameNPath, CommandLineParams)) + { + bFoundProcessRunning = true; + process = rProcess; + indexFound = i; + } + } + break; + } + } + } + + // PID_Hint Worked, no need to continue... + if (bFoundProcessRunning) + return bFoundProcessRunning; + + // Iterate all others + for (int i = 0; i < runningProcesses.Length; ++i) + { + Diag.Process rProcess = runningProcesses[i]; + if (rProcess != null && (rProcess.Id != PID_Hint)) // Ignore PID_Hint * already been tried * + { + string ProcessFileNameNPath_Local; + string CommandLineParams_Local; + if (ProcessW.GetCommandLineArgumentsForProcessPID(rProcess.Id, out ProcessFileNameNPath_Local, out CommandLineParams_Local)) + { + // See if we find a match + if (ProcessFileExeNCommandLineParamsComparer_FoundMatch(ProcessFileNameNPath_Local, CommandLineParams_Local, ProcessFileNameNPath, CommandLineParams)) + { + bFoundProcessRunning = true; + process = rProcess; + indexFound = i; + break; + } + } + } + } + return bFoundProcessRunning; + } + catch (Exception) { /* ignore */ } + return false; + } + + /// + /// Returns true if the passed in ProcessExeFileNameNPath, CommandLinePrms match _Left and _Right + /// + /// Process FileNameNPath to compare to Right + /// CommandLine Parameters to compare to Right + /// Process FileNameNPath to compare to Left + /// CommandLine Parameters to compare to Left + /// true if _left and _right match, false otherwise + public static bool ProcessFileExeNCommandLineParamsComparer_FoundMatch(string ProcessFileNameNPath_Left, string CommandLineParams_Left, string ProcessFileNameNPath_Right, string CommandLineParams_Right) + { + if ((String.Compare(ProcessFileNameNPath_Left.Trim(), ProcessFileNameNPath_Right.Trim(), true) == 0) && + (String.Compare(CommandLineParams_Left.Trim(), CommandLineParams_Right.Trim(), true) == 0)) + { + return true; + } + return false; + } + + #endregion + } +} diff --git a/Process/ServiceW.cs b/Process/ServiceW.cs new file mode 100644 index 0000000..f1db082 --- /dev/null +++ b/Process/ServiceW.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ServiceProcess; +using Yaulw.Win32; +using System.Runtime.InteropServices; + +namespace Yaulw.Process +{ + /// + /// Wrapper Class around System.ServiceProcess's ServiceControl Class, + /// ~Little helper functions to make sure that we can safely interact with Services + /// + public static class ServiceW + { + /// + /// Checks to see if the service exists on the local computer + /// + /// Name of Service to Check + /// true if it exists, false otherwise + public static bool DoesServiceExist(string ServiceName) + { + try + { + ServiceController scm = new ServiceController(ServiceName); + if (scm != null && !String.IsNullOrEmpty(scm.ServiceName)) + { + scm.Dispose(); + return true; + } + } + catch (Exception) { /* ignore */ } + return false; + } + + /// + /// Quick check to see if the service is running + /// + /// Name of Service to Check + /// true if the service is running, false otherwise, if it doesn't exist returns true + public static bool IsServiceRunning(string ServiceName) + { + ServiceController scm = null; + try + { + scm = new ServiceController(ServiceName); + } + catch (Exception) { /* ignore */ } + + if (scm != null) + { + bool bIsRunning = (scm.Status == ServiceControllerStatus.Running); + scm.Dispose(); + return bIsRunning; + } + else + return false; + } + + /// + /// Use this to Restart the Service + /// + /// Name of Service to Check + /// Set to true to try to force a Service Close by killing the Process, if the service is hanging + /// Number of Seconds to wait for Service To Close/Start + /// Number of Seconds to wait inbetween closing and starting + /// true if successful, false otherwise + public static bool RestartService(string ServiceName, bool bKillServiceByForceIfNeedBe = true, int WaitTimeoutInSeconds = 240, int nStartupDelayInSeconds = 2) + { + // Stop the Service + if (!StopService(ServiceName, bKillServiceByForceIfNeedBe, WaitTimeoutInSeconds)) + return false; + + // Delay Starting the Services for 'n' seconds + if (nStartupDelayInSeconds > 0) + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(nStartupDelayInSeconds)); + + // Start the Service + bool bIsStarted = StartService(ServiceName, WaitTimeoutInSeconds); + return bIsStarted; + } + + /// + /// Use this to start a service + /// + /// Name of Service to Check + /// Number of seconds to wait for Service to Start + /// true if successful, false otherwise + public static bool StartService(string ServiceName, int WaitTimeoutInSeconds = 240) + { + ServiceController scm = null; + try + { + scm = new ServiceController(ServiceName); + + // Wait for 'Stopped' State + if (scm.Status != ServiceControllerStatus.Running) + { + scm.Start(); + if (WaitTimeoutInSeconds > 0) + scm.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(WaitTimeoutInSeconds)); + } + } + catch (Exception) { /* ignore */ } + + if (scm != null) + { + bool bSuccess = (scm.Status == ServiceControllerStatus.Running); + scm.Dispose(); + return bSuccess; + } + else + return false; + } + + /// + /// Use this to stop a service + /// + /// Name of Service to Check + /// Set to true to try to force a Service Close by killing the Process, if the service is hanging + /// Number of seconds to wait for Service to Stop + /// true if successful, false otherwise + public static bool StopService(string ServiceName, bool bKillServiceByForceIfNeedBe = true, int WaitTimeoutInSeconds = 240) + { + ServiceController scm = null; + try + { + scm = new ServiceController(ServiceName); + + // Wait for 'Stopped' State + if (scm.Status != ServiceControllerStatus.Stopped) + { + scm.Stop(); + if (WaitTimeoutInSeconds > 0) + scm.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(WaitTimeoutInSeconds)); + } + } + catch (Exception) { /* ignore */ } + + try + { + if (scm != null) + { + bool bSuccess = (scm.Status == ServiceControllerStatus.Stopped); + + // Always try Killing Service By Force, even if bSuccess is true, + // just to try to guarantee that the service is dead for good + if (bKillServiceByForceIfNeedBe) + bSuccess = StopServiceByForce(scm) || bSuccess; + + scm.Dispose(); + return bSuccess; + } + else + return false; + } + catch (Exception) { /* ignore */ } + return false; + } + + /// + /// Brutal way to stop a service * Not Recommended *, + /// according to article: + /// + /// + /// + /// a Service Controller Object whose PID you would like to kill + /// true if successfull, false otherwise + private static bool StopServiceByForce(ServiceController scm) + { + Structures.SERVICE_STATUS_PROCESS ssp = new Structures.SERVICE_STATUS_PROCESS(); + int ignored; + + try + { + // Obtain information about the service, and specifically its hosting process, + // from the Service Control Manager. + if (!Advapi32.QueryServiceStatusEx(scm.ServiceHandle.DangerousGetHandle(), Definitions.SC_STATUS_PROCESS_INFO, ref ssp, Marshal.SizeOf(ssp), out ignored)) + { + //throw new Exception("Couldn't obtain service process information."); + return false; + } + + // A few quick sanity checks that what the caller wants is *possible*. + if (ssp.dwServiceType != Definitions.SERVICE_WIN32_OWN_PROCESS) + { + //throw new Exception("Can't wait for the service's hosting process to exit because there may be multiple services in the process (dwServiceType is not SERVICE_WIN32_OWN_PROCESS"); + return false; + } + + if ((ssp.dwServiceFlags & Definitions.SERVICE_RUNS_IN_SYSTEM_PROCESS) != 0) + { + //throw new Exception("Can't wait for the service's hosting process to exit because the hosting process is a critical system process that will not exit (SERVICE_RUNS_IN_SYSTEM_PROCESS flag set)"); + return false; + } + + if (ssp.dwProcessId == 0) + { + //throw new Exception("Can't wait for the service's hosting process to exit because the process ID is not known."); + return false; + } + + // Try closing the Process Manually + bool bCloseSuccess = PStarter.KillProcess((uint)ssp.dwProcessId, false, 1, false, 2); + return bCloseSuccess; + } + catch (Exception) { /* ignore */ } + return false; + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cc3606a --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Yaulw")] +[assembly: AssemblyDescription("Yet Another Utility Library for .Net Windows")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Ideas To Action, Inc.")] +[assembly: AssemblyProduct("Yaul")] +[assembly: AssemblyCopyright("Copyright Daniel A. Romischer © 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2f7e3593-1fc7-45d8-b2f6-7e51d5161763")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Registry/RegKey.cs b/Registry/RegKey.cs new file mode 100644 index 0000000..186ec95 --- /dev/null +++ b/Registry/RegKey.cs @@ -0,0 +1,487 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Win32; + +using Yaulw.Tools; + +namespace Yaulw.Registry +{ + #region RegKey Enums + + /// + /// Registry Root Key + /// + public enum HKEYRoot + { + ClassesRoot, + CurrentUser, + LocalMachine, + } + + /// + /// Registry Sub Key + /// + public enum KeySub + { + Software, + Custom + } + + #endregion + + /// + /// Wrapper Class around Registry Functionallity, + /// allows for easy reading/writing to the registry, especially HKEY_CURRENT_USER\Software + /// ~If you use this class directly make sure to call dispose(), + /// otherwise, use the Public Static Helper Functions to do the work for you + /// + public class RegKey : IDisposable + { + #region Private Members + + private RegistryKey _RegKey; + private bool _disposed = false; + + #endregion + + #region Constructors + + /// + /// Use this to Open or Automatically Create the specified Key in CURRENT_USER\Software\Key. + /// + /// Specify Key to Open + /// Set to true to Open, false to Create if not Exists + public RegKey(string Key, bool bReadOnly = true) : this(HKEYRoot.CurrentUser, KeySub.Software, Key, bReadOnly) {} + + /// + /// Use this to Open or Automatically Create a Registry Key + /// + /// specify registry root + /// specify a sub or custom + /// key you want to open / create + /// true if you want to force only reading it + private RegKey(HKEYRoot root, KeySub sub, string Key, bool bReadOnly) + { + string KeyToOpen = String.Empty; + if (sub == KeySub.Custom) + KeyToOpen = Key; + else + KeyToOpen = sub.ToString() + "\\" + Key; + + // Read Only Permission + RegistryKeyPermissionCheck permission = RegistryKeyPermissionCheck.ReadSubTree; + if (!bReadOnly) + permission = RegistryKeyPermissionCheck.ReadWriteSubTree; + + // Open or Create, if it doesn't exist and we have writable set + switch (root) + { + case HKEYRoot.ClassesRoot: + if (bReadOnly) + _RegKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(KeyToOpen); + else + _RegKey = Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(KeyToOpen, permission); + break; + + case HKEYRoot.CurrentUser: + if (bReadOnly) + _RegKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(KeyToOpen, permission); + else + _RegKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(KeyToOpen, permission); + break; + + case HKEYRoot.LocalMachine: + if (bReadOnly) + _RegKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(KeyToOpen, permission); + else + _RegKey = Microsoft.Win32.Registry.LocalMachine.CreateSubKey(KeyToOpen, permission); + break; + } + } + + /// + /// Finalizer + /// + ~RegKey() + { + Dispose(true); + } + + #endregion + + #region GetValue Registry Functions + + /// + /// Generic Registry KeyValue Getter Function + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// The Value to get from the Registry + /// Default value to use if nothing was retrieved * Error occured * + /// Value found or Default Value + public T GetVal(string strKeyValue, T DefaultValue) + { + T retVal = DefaultValue; + try + { + if (_RegKey != null) + { + object keyvalue = _RegKey.GetValue(strKeyValue); + string strvalue = string.Empty; + if (ObjTool.IsNotNullAndNotEmpty(keyvalue)) + { + #region First Handle the Value as a string + + // Handle Multi-Strings + if (keyvalue is object[]) + { + string keyVal2 = string.Empty; + string[] strVal2 = (string[])keyvalue; + foreach (string str in strVal2) + { + keyVal2 += str; + } + strvalue = keyVal2; + } + else + { + // Handle Single-Strings + strvalue = keyvalue.ToString(); + } + + #endregion + + // Now cast the Object Return type * Handle each object differently * + retVal = ObjTool.ConvertStringToObj(strvalue); + } + } + } + catch (Exception) { /* ignore */ } + return retVal; + } + + /// + /// Binary Registry KeyValue Getter Function + /// + /// The Value to get from the Registry + /// an array of Bytes found in the Registry, or null if none found, or error occured + public byte[] GetVal(string strKeyValue) + { + byte[] retVal = null; + try + { + string StringValue = GetVal(strKeyValue, String.Empty); + if (!String.IsNullOrEmpty(StringValue)) + { + Byte[] ByteValue = new Byte[StringValue.Length]; + for (int i = 0; i < ByteValue.Length; ++i) + { + string strByte = System.Convert.ToString(StringValue[i]); + ByteValue[i] = byte.Parse(strByte); + } + retVal = ByteValue; + } + } + catch (Exception) { /* ignore */ } + return retVal; + } + + #endregion + + #region SetValue Registry Functions + + /// + /// Generic Registry KeyValue Setter Function + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// The Value to write to in the Registry + /// The Value to set to the Registry + /// true if successful, false otherwise + public bool SetVal(string strKeyValue, T Value) + { + bool bRetVal = false; + try + { + if (_RegKey != null) + { + if (Value is System.Int32) + { + _RegKey.SetValue(strKeyValue, Value, RegistryValueKind.DWord); + } + else if (Value is System.Int64) + { + _RegKey.SetValue(strKeyValue, Value, RegistryValueKind.QWord); + } + else + { + _RegKey.SetValue(strKeyValue, Value, RegistryValueKind.String); + } + bRetVal = true; + } + } + catch (Exception) { /* ignore */ } + return bRetVal; + } + + /// + /// Generic Registry KeyValue Setter Function + /// + /// The Value to write to in the Registry + /// A binary array of bytes to write to the Registry + /// true if successful, false otherwise + public bool SetVal(string strKeyValue, byte[] ByteValue) + { + try + { + _RegKey.SetValue(strKeyValue, ByteValue, RegistryValueKind.Binary); + return true; + } + catch (Exception) { /* ignore */ } + return false; + } + + #endregion + + #region Delete Registry Function + + /// + /// Registry KeyValue Delete Function + /// + /// The Value to delete in the Registry + /// true if successful, false otherwise + public bool DeleteKey(string strKeyValue) + { + try + { + if (_RegKey != null) + { + _RegKey.DeleteValue(strKeyValue, false); + return true; + } + } + catch (Exception) { /* ignore */ } + return false; + } + + #endregion + + #region Public Static Helper Functions + + /// + /// Generic Static Registry Writer Function * Allows writing to HKEY\CURRENT_USER\SOFTWARE * + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// SubKey i.e. Microsoft, Microsoft\Windows + /// Value to write to + /// Value to write + /// true if successful, false otherwise + public static bool SetKey(String SubKey, String KeyValue, T Value) + { + bool bRetVal = false; + using (RegKey reg = new RegKey(HKEYRoot.CurrentUser, KeySub.Software, SubKey, false)) + { + bRetVal = reg.SetVal(KeyValue, Value); + } + return bRetVal; + } + + /// + /// Generic Static Registry Writer For Binary Function * Allows writing to HKEY\CURRENT_USER\SOFTWARE * + /// + /// SubKey i.e. Microsoft, Microsoft\Windows + /// Value to write to + /// Binary Value to write + /// true if successful, false otherwise + public static bool SetKey(String SubKey, String KeyValue, byte[] ByteValue) + { + bool bRetVal = false; + using (RegKey reg = new RegKey(HKEYRoot.CurrentUser, KeySub.Software, SubKey, false)) + { + bRetVal = reg.SetVal(KeyValue, ByteValue); + } + return bRetVal; + } + + /// + /// Generic Static Registry Writer Function * Allows writing to any key * + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// RootKey as defined in this Class + /// SubKey i.e. Software, Software\Microsoft + /// Value to write to + /// Value to write + /// true if successful, false otherwise + public static bool SetKey(HKEYRoot rootKey, String SubKey, String KeyValue, T Value) + { + bool bRetVal = false; + using (RegKey reg = new RegKey(rootKey, KeySub.Custom, SubKey, false)) + { + bRetVal = reg.SetVal(KeyValue, Value); + } + return bRetVal; + } + + /// + /// Generic Static Registry Writer For Binary Function * Allows writing to any key * + /// + /// RootKey as defined in this Class + /// SubKey i.e. Software, Software\Microsoft + /// Value to write to + /// Binary Value to write + /// true if successful, false otherwise + public static bool SetKey(HKEYRoot rootKey, String SubKey, String KeyValue, byte[] ByteValue) + { + bool bRetVal = false; + using (RegKey reg = new RegKey(rootKey, KeySub.Custom, SubKey, false)) + { + bRetVal = reg.SetVal(KeyValue, ByteValue); + } + return bRetVal; + } + + /// + /// Static Registry Delete Function * Allows Deleting from HKEY\CURRENT_USER\SOFTWARE * + /// + /// SubKey i.e. Microsoft, Microsoft\Windows + /// Value to delete + /// true if successful, false otherwise + public static bool DeleteKey(String SubKey, String KeyValue) + { + bool bRetVal = false; + using (RegKey reg = new RegKey(HKEYRoot.CurrentUser, KeySub.Software, SubKey, false)) + { + bRetVal = reg.DeleteKey(KeyValue); + } + return bRetVal; + } + + /// + /// Static Registry Delete Function * Allows Deleting any key * + /// + /// RootKey as defined in this Class + /// SubKey i.e. Software, Software\Microsoft + /// Value to delete + /// true if successful, false otherwise + public static bool DeleteKey(HKEYRoot rootKey, String SubKey, String KeyValue) + { + bool bRetVal = false; + using (RegKey reg = new RegKey(rootKey, KeySub.Custom, SubKey, false)) + { + bRetVal = reg.DeleteKey(KeyValue); + } + return bRetVal; + } + + /// + /// Generic Static Registry Reader Function * Allows reading from HKEY\CURRENT_USER\SOFTWARE * + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// SubKey i.e. Microsoft, Microsoft\Windows + /// Value to write to + /// Default Value to return if none found or error occured + /// true if successful, false otherwise + public static T GetKey(String SubKey, String KeyValue, T DefaultValue) + { + T tRetVal = default(T); + using (RegKey reg = new RegKey(HKEYRoot.CurrentUser, KeySub.Software, SubKey, true)) + { + tRetVal = reg.GetVal(KeyValue, DefaultValue); + } + return tRetVal; + } + + /// + /// Generic Static Registry Reader For Binary Function * Allows reading from HKEY\CURRENT_USER\SOFTWARE * + /// + /// SubKey i.e. Microsoft, Microsoft\Windows + /// Value to write to + /// true if successful, false otherwise + public static Byte[] GetKey(String SubKey, String KeyValue) + { + Byte[] retByte = null; + using (RegKey reg = new RegKey(HKEYRoot.CurrentUser, KeySub.Software, SubKey, true)) + { + retByte = reg.GetVal(KeyValue); + } + return retByte; + } + + /// + /// Generic Static Registry Reader Function * Allows reading from any key * + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// RootKey as defined in this Class + /// SubKey i.e. Software, Software\Microsoft + /// Value to write to + /// Default Value to return if none found or error occured + /// true if successful, false otherwise + public static T GetKey(HKEYRoot rootKey, String SubKey, String KeyValue, T DefaultValue) + { + T tRetVal = default(T); + using (RegKey reg = new RegKey(rootKey, KeySub.Custom, SubKey, true)) + { + tRetVal = reg.GetVal(KeyValue, DefaultValue); + } + return tRetVal; + } + + /// + /// Generic Static Registry Reader For Binary Function * Allows reading from any key * + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// RootKey as defined in this Class + /// SubKey i.e. Software, Software\Microsoft + /// Value to write to + /// true if successful, false otherwise + public static Byte[] GetKey(HKEYRoot rootKey, String SubKey, String KeyValue) + { + Byte[] retByte = null; + using (RegKey reg = new RegKey(rootKey, KeySub.Custom, SubKey, true)) + { + retByte = reg.GetVal(KeyValue); + } + return retByte; + } + + #endregion + + #region IDisposable Members + + /// + /// Dispose the Registry Handle + /// + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer + GC.SuppressFinalize(this); + } + + /// + /// Dispose the Registry Handle + /// + /// true, if called from within + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { +#if NET4 + if (disposing) + { + if (_RegKey != null) + _RegKey.Dispose(); // Works in .Net 4.0 Only + } +#endif + + // Indicate that the instance has been disposed. + _RegKey = null; + _disposed = true; + } + } + + #endregion + } +} diff --git a/Structs/sbstring.cs b/Structs/sbstring.cs new file mode 100644 index 0000000..f42bd2a --- /dev/null +++ b/Structs/sbstring.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Utilities.GenericUtilities.Structs +{ + /// + /// String Builder Wrapper struct in order to be able to use += + /// + public struct sbstring + { + // StringBuilder to wrapp + private StringBuilder _sb; + + + public static string operator+ (sbstring op1, string op2) + { + op1.SBInit(); + op1._sb.Append(op2); + return op1._sb.ToString(); + } + + //public static sbstring operator +(sbstring op1, string op2) + //{ + // op1._sb.Append(op2); + // return op1; + //} + + /// + /// Call this function to initialize the StringBuilder * must be called by all ops first * + /// + private void SBInit() { if (_sb == null) _sb = new StringBuilder(); } + } +} diff --git a/Thread/DispatcherThread.cs b/Thread/DispatcherThread.cs new file mode 100644 index 0000000..8333204 --- /dev/null +++ b/Thread/DispatcherThread.cs @@ -0,0 +1,507 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Sys = System.Threading; +using System.Reflection; +using Yaulw.WPF; +using System.Threading; +using System.Windows.Threading; +using Yaulw.Other; + +namespace Yaulw.Thread +{ + /// + /// + /// + public enum DispatcherThreatType + { + WPF, + // Winforms // ~Not implmented + // Silverlight? // ~Not implemented + } + + /// + /// + /// + public class WindowType + { + public Type T; + public Enum Key; + public bool IsSingleton; + public bool IsPerformanceLoaded; + + public WindowType(Type t, Enum Key, bool IsSingleton, bool IsPerformanceLoaded) + { + this.T = t; + this.Key = Key; + this.IsSingleton = IsSingleton; + this.IsPerformanceLoaded = IsPerformanceLoaded; + } + } + + /// + /// + /// + public class WindowAttributes + { + public IntPtr hWndOwner; + public uint Height; + public uint Width; + public int Top; + public int Left; + public string Title; + + public WindowAttributes(IntPtr hWndOwner, uint Height, uint Width, int Top, int Left, string Title) + { + this.hWndOwner = hWndOwner; + this.Height = Height; + this.Width = Width; + this.Top = Top; + this.Left = Left; + this.Title = Title; + } + + public WindowAttributes(IntPtr hWndOwner, int Top, int Left) + { + this.hWndOwner = hWndOwner; + this.Height = 0; + this.Width = 0; + this.Top = Top; + this.Left = Left; + this.Title = String.Empty; + } + + public WindowAttributes(IntPtr hWndOwner) + { + this.hWndOwner = hWndOwner; + this.Height = 0; + this.Width = 0; + this.Top = int.MinValue; + this.Left = int.MinValue; + this.Title = String.Empty; + } + + } + + #region Window Manager Definition + + /// + /// Abstract Base Class all Window Managers must inherit from. + /// + /// + public abstract class WindowManager : IDisposable + { + #region Private Members + + private Dictionary> s_WindowsToManage = new Dictionary>(); + + #endregion + + #region Construction + + /// + /// Construct a Window Manager Object + /// + /// Thread Dispatcher Object + /// Window Types this WindowManager is managing + /// true, to load performance enabled windows upon construction + public WindowManager(Dispatcher dispatcher, WindowType[] windowTypes, bool PerformanceLoad) + { + Dispatcher = dispatcher; + WindowTypes = windowTypes; + if (Dispatcher == null) + throw new ArgumentException("dispatcher can't be null"); + if (windowTypes == null || windowTypes.Length < 1) + throw new ArgumentException("windowTypes can't be null or empty"); + + // Add All Window Types + foreach (WindowType type in windowTypes) + { + // Add the Type to be managed + AddWindowTypeToManage(type); + + // perform performance optimization + if (PerformanceLoad && type.IsPerformanceLoaded) + { + object o = CreateInstance(type); + AddObject(type, o); + } + } + } + + #endregion + + #region Public Properties + + /// + /// Returns the Dispatcher Thread responsible for this Window Manager + /// + public Dispatcher Dispatcher { get; private set; } + + /// + /// Returns all the Window Types that have been set to be managed by this Window Manager + /// + public WindowType[] WindowTypes { get; private set; } + + #endregion + + #region Protected Common Window Object Operations + + /// + /// Create a new Object Instance of type + /// + /// type of object to create + /// a new object for type, or null + protected object CreateInstance(WindowType window) + { + object o = null; + if (window != null) + o = Activator.CreateInstance(window.T); + return o; + } + + /// + /// Create a new Object Instance of type + /// + /// type of object to create + /// a new object for type, or null + protected object CreateInstance(Enum windowKey) + { + return CreateInstance(GetType(windowKey)); + } + + /// + /// + /// + /// + /// + protected List GetObjects(Enum windowKey) + { + foreach (WindowType type in s_WindowsToManage.Keys) + { + if (type.Key.ToString() == windowKey.ToString()) + return s_WindowsToManage[type]; + } + return null; + } + + /// + /// + /// + /// + /// + protected List GetObjects(WindowType window) + { + foreach (WindowType type in s_WindowsToManage.Keys) + { + if (type.Key.ToString() == window.Key.ToString()) + return s_WindowsToManage[type]; + } + return null; + } + + /// + /// + /// + /// + /// + protected WindowType GetType(Enum windowKey) + { + foreach (WindowType type in s_WindowsToManage.Keys) + { + if (type.Key.ToString() == windowKey.ToString()) + return type; + } + return null; + } + + /// + /// + /// + /// + /// + /// + protected bool AddObject(WindowType window, object o) + { + if (AddWindowTypeToManage(window)) + { + List objects = GetObjects(window); + if (objects.Count == 0) + { + objects.Add(o); + return true; + } + else if (objects.Count > 0 && !window.IsSingleton) + { + objects.Add(o); + return true; + } + } + return false; + } + + /// + /// + /// + /// + /// + /// + protected bool AddObject(Enum windowKey, object o) + { + WindowType type = GetType(windowKey); + if (type != null) + { + List objects = GetObjects(windowKey); + if (objects.Count == 0) + { + objects.Add(o); + return true; + } + else if (objects.Count > 0 && !type.IsSingleton) + { + objects.Add(o); + return true; + } + } + return false; + } + + /// + /// + /// + /// + /// + protected bool DeleteObject(WindowType window) + { + List objects = GetObjects(window); + if (objects != null && objects.Count > 0) + { + int nIndex = objects.Count - 1; + if (objects[nIndex] is IDisposable) + ((IDisposable)objects[nIndex]).Dispose(); + objects.RemoveAt(nIndex); + return true; + } + return false; + } + + /// + /// + /// + /// + /// + protected bool DeleteObject(Enum windowKey) + { + List objects = GetObjects(windowKey); + if (objects != null && objects.Count > 0) + { + int nIndex = objects.Count - 1; + if (objects[nIndex] is IDisposable) + ((IDisposable)objects[nIndex]).Dispose(); + objects.RemoveAt(nIndex); + return true; + } + return false; + } + + /// + /// + /// + protected void ClearAllObjects() + { + // Iterate thru all objects and call dispose on them if they support it, + // then clear main dictionary + foreach (WindowType type in s_WindowsToManage.Keys) + while (DeleteObject(type)) ; + + s_WindowsToManage.Clear(); + } + + /// + /// + /// + /// + /// + protected bool AddWindowTypeToManage(WindowType window) + { + if (window != null && !IsWindowTypeManaged(window)) + s_WindowsToManage[window] = new List(); + + return (IsWindowTypeManaged(window)); + } + + /// + /// + /// + /// + /// + protected bool IsWindowTypeManaged(WindowType window) + { + if (window != null) + { + foreach (WindowType type in s_WindowsToManage.Keys) + { + if (type.Key.ToString() == window.Key.ToString()) + return true; + } + } + return false; + } + + #endregion + + #region Abtract Methods + + public delegate object ShowDelegate(Enum windowKey, WindowAttributes attributes, object tag); + public abstract object ShowWindow(Enum windowKey, WindowAttributes attributes, object tag); + public abstract object ShowDialog(Enum windowKey, WindowAttributes attributes, object tag); + + public delegate bool HideCloseDelegate(Enum windowKey); + public abstract bool HideWindow(Enum windowKey); + public abstract bool CloseWindow(Enum windowKey); + + public delegate bool ActionDelegate(Enum windowKey, DelegateCollection.Bool_Param1_Window_Func action); + public abstract bool RunAction(Enum windowKey, DelegateCollection.Bool_Param1_Window_Func action); + + public abstract void Dispose(); + + #endregion + + #region Public Methods + + /// + /// Get All Window Objects for a Window, in a readonly fashion + /// + /// + /// + public object[] GetAllWindowObjectsForWindow(Enum windowKey) + { + List objects = GetObjects(windowKey); + if (objects != null) + return objects.ToArray(); + else + return null; + } + + #endregion + } + + #endregion + + #region Dispatcher Thread + + /// + /// + /// + static class DispatcherThread + { + #region Private Static Members + + private static Sys.Thread s_DispatcherThread = null; + private static bool s_DispatcherThreadIsInitialized = false; + + private static WPFWindowManager s_wpfWindowManager = null; + private static DispatcherThreatType s_useWindowManagerType = DispatcherThreatType.WPF; + private static WindowType[] s_windowTypes = null; + private static bool s_PerformPerformanceLoad = false; + + #endregion + + #region Private Static Methods + + /// + /// Message Loop Thread for ButtonForm + /// + private static void ThreadProc() + { + // Create WindowManager Object on this thread *Allow global access to the object* + if (s_useWindowManagerType == DispatcherThreatType.WPF) + s_wpfWindowManager = new WPFWindowManager(Dispatcher.CurrentDispatcher, s_windowTypes, s_PerformPerformanceLoad); + + s_DispatcherThreadIsInitialized = true; // always set to true to allow caller to exit out + if (s_wpfWindowManager != null) + { + //Enter Dispatcher Message Loop + System.Windows.Threading.Dispatcher.Run(); + } + } + + #endregion + + #region Public Static Methods + + /// + /// Retrieve the Window Manager for this Dispatcher Thread + /// + /// a valid window Manager or null, if none + public static WindowManager GetWMForDispatcher() + { + if (s_DispatcherThreadIsInitialized) + { + if (s_useWindowManagerType == DispatcherThreatType.WPF && s_wpfWindowManager != null) + return s_wpfWindowManager; + } + return null; + } + + /// + /// Use this function to start the Dispatcher ThreadProc(above), if needed. COM+ can shutdown the thread anytime, + /// we need to make sure that the thread is alive BEFORE calling WindowManager + /// + public static void Start(DispatcherThreatType threatType, WindowType[] windowTypes, bool PerformPerformanceLoad, bool bWait) + { + if (s_DispatcherThread != null && s_DispatcherThread.IsAlive) + { + return; + } + else + { + s_DispatcherThreadIsInitialized = false; + + // Specify Thread Attributes + s_useWindowManagerType = threatType; + s_windowTypes = windowTypes; + s_PerformPerformanceLoad = PerformPerformanceLoad; + + // Start a new Thread so it can become the Message Loop + s_DispatcherThread = new Sys.Thread(new Sys.ThreadStart(DispatcherThread.ThreadProc)); + + // GUI components require the thread to be STA; otherwise throws an error + s_DispatcherThread.SetApartmentState(ApartmentState.STA); + s_DispatcherThread.Start(); + + // Make sure the Application Object is running + // (COM will eventually just time out otherwise) + if (bWait) // Syncronous call + { + while (!s_DispatcherThreadIsInitialized) + System.Threading.Thread.Sleep(20); + } + } + } + + /// + /// Terminates all ButtonForm Objects and the Message Dispatcher Thread *do this only on shutdown* + /// + public static void Stop() + { + if (s_DispatcherThreadIsInitialized) + { + // Enforce that all WindowManagers support IDispose + if (s_wpfWindowManager != null) + { + s_wpfWindowManager.Dispose(); + s_wpfWindowManager = null; + } + s_DispatcherThread.Abort(); + s_DispatcherThreadIsInitialized = false; + s_DispatcherThread = null; + } + } + + #endregion + } + + #endregion +} diff --git a/Thread/SingleThreadTimer.cs b/Thread/SingleThreadTimer.cs new file mode 100644 index 0000000..9d95d45 --- /dev/null +++ b/Thread/SingleThreadTimer.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Timers; + +namespace Yaulw.Thread +{ + /// + /// Single Threaded Timer * only calls the elapsed event handler once and only + /// will call it again, if the elapsed event handler returns + /// + public class SingleThreadTimer + { + #region Private Members + + private STElapsedEventHandler _elapsedHandler = null; + private uint _IntervalMilliseconds = 0; + private System.Threading.Thread _thread = null; + + #endregion + + #region Construction + + /// + /// Creates a new Single-threaded System.Timer + /// + /// Event Handler for Timer + /// Interval in Miliseconds + /// True to start the timer upon creation, false otherwise + /// A Timer Object, which should be Disposed by Caller + public SingleThreadTimer(STElapsedEventHandler ElapsedHandler, uint IntervalMilliseconds, bool StartEnabled) + { + _elapsedHandler = ElapsedHandler; + _IntervalMilliseconds = IntervalMilliseconds; + if (_elapsedHandler == null || _IntervalMilliseconds == 0) + throw new ArgumentException("Invalid ElapsedHandler or Milliseconds. ElapsedHandler can not be null. Milliseconds can not be 0."); + + if (StartEnabled) + Start(); + } + + #endregion + + #region Private Helpers + + /// + /// Main DoWork() Function, calls the ElapsedHandler at every interval + /// * Single threaded * ElapsedHandler will only get called again, once it returns + /// + private void STimerThreadMethod() + { + do + { + if (_IntervalMilliseconds > 0) + System.Threading.Thread.Sleep((int)_IntervalMilliseconds); + + if (_elapsedHandler != null) + _elapsedHandler(this, new STElapsedEventArgs(DateTime.Now)); + } + while (true); + } + + #endregion + + #region Public Delegates + + /// + /// ElapsedEvent Handler is called when the time interval elapsed + /// + /// Sends the STimer Object + /// sends the Timestampe when the interval elapsed + public delegate void STElapsedEventHandler(object sender, STElapsedEventArgs e); + + /// + /// ElapsedEventArgs are called with the time stamp when the Event was raised + /// + public class STElapsedEventArgs : EventArgs + { + public DateTime SignalTime { get; private set; } + public STElapsedEventArgs(DateTime SignalTime) { this.SignalTime = SignalTime; } + } + + #endregion + + #region Public Methods + + /// + /// Manually Start the STimer + /// + public bool Start() + { + Stop(); // First Stop(), an existing Timer + return Start(_IntervalMilliseconds); + } + + /// + /// Manually Start the STimer at a new Interval + /// + /// Interval as a TimeSpan + public bool Start(TimeSpan tsInterval) + { + Stop(); // First Stop(), an existing Timer + return Start((uint)tsInterval.TotalMilliseconds); + } + + /// + /// Manually Start the STimer at a new Interval + /// + /// Interval in Miliseconds + public bool Start(uint IntervalMiliseconds) + { + try + { + Stop(); // First Stop(), an existing Timer + if (IntervalMiliseconds > 0) + _IntervalMilliseconds = IntervalMiliseconds; + if (_thread == null) + _thread = TStarter.StartThread(STimerThreadMethod, ("STimer-" + _elapsedHandler.Method.Name), System.Threading.ApartmentState.MTA, false, System.Threading.ThreadPriority.Normal); + return ((_thread != null) && (_thread.IsAlive)); + } + catch (Exception) { /* ignore */ } + return false; + } + + /// + /// Manually Stop the STimer + /// + public void Stop() + { + try + { + if (_thread != null) + _thread.Abort(); + } + catch (Exception) { /* ignore */ } + _thread = null; + } + + #endregion + + } +} diff --git a/Thread/TStarter.cs b/Thread/TStarter.cs new file mode 100644 index 0000000..20bf8c5 --- /dev/null +++ b/Thread/TStarter.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Collections.Specialized; + +namespace Yaulw.Thread +{ + /// + /// Used to Manage/Launch Threads In an Application + /// + public static class TStarter + { + #region Public Delegates + + /// + /// Thread Delegate with a single Object as a parameter + /// + /// object to be passed to Delegate + public delegate void ParameterizedThreadMethod(object o); + + /// + /// No Parameter Thread Delegate + /// + public delegate void ThreadMethod(); + + #endregion + + #region Private Static Members + + private static Dictionary _StartedNamedThreads = new Dictionary(); + + #endregion + + #region Public Statics + + /// + /// Starts a Parameterized thread + /// + /// specifcy a method to invoke + /// specify a parameter to pass to the method + /// If you specify a ThreadName, it must be Unique to the Application + /// ApartmentState + /// Is Background Thread + /// set thread priority + /// a thread object if successful, null otherwise + public static System.Threading.Thread StartParameterizedThread(ParameterizedThreadMethod method, object parameter, string threadName = "", ApartmentState Apartment = ApartmentState.MTA, bool IsBackground = false, ThreadPriority priority = ThreadPriority.Normal) + { + // If a threadName is defined, make sure that it is unique to the application + if (!String.IsNullOrEmpty(threadName) && _StartedNamedThreads.ContainsKey(threadName)) + return null; + + System.Threading.Thread thread = null; + if (method != null && parameter != null) + { + thread = new System.Threading.Thread(new ParameterizedThreadStart(method)); + if (!String.IsNullOrEmpty(threadName)) + thread.Name = threadName; + + // Start the Thread + thread.SetApartmentState(Apartment); + thread.Priority = priority; + thread.IsBackground = IsBackground; + thread.Start(parameter); + + // Save the Thread in the Dictionary + if (!String.IsNullOrEmpty(threadName)) + _StartedNamedThreads[threadName] = thread; + } + return thread; + } + + /// + /// Starts a thread without Parameters + /// + /// specifcy a method to invoke + /// If you specify a ThreadName, it must be Unique to the Application + /// ApartmentState + /// Is Background Thread + /// set thread priority + /// a thread object if successful, null otherwise + public static System.Threading.Thread StartThread(ThreadMethod method, string threadName = "", ApartmentState Apartment = ApartmentState.MTA, bool IsBackground = false, ThreadPriority priority = ThreadPriority.Normal) + { + // If a threadName is defined, make sure that it is unique to the system + if (!String.IsNullOrEmpty(threadName) && _StartedNamedThreads.ContainsKey(threadName)) + return null; + + System.Threading.Thread thread = null; + if (method != null) + { + thread = new System.Threading.Thread(new ThreadStart(method)); + if (!String.IsNullOrEmpty(threadName)) + thread.Name = threadName; + + // Start the Thread + thread.SetApartmentState(Apartment); + thread.Priority = priority; + thread.IsBackground = IsBackground; + thread.Start(); + + // Save the Thread in the Dictionary + if (!String.IsNullOrEmpty(threadName)) + _StartedNamedThreads[threadName] = thread; + } + return thread; + + } + + /// + /// Retrieve all Named threads that were started thru here that are still running + /// + /// an array of running named threads, null if none + public static System.Threading.Thread[] GetStartedNamedThreadsThatAreStillRunning() + { + List threads = new List(); + List threadNamesThatAreNoLongerAlive = new List(); + + // Get Named Alive Threads + foreach (System.Threading.Thread thread in _StartedNamedThreads.Values) + { + if (thread.IsAlive) + threads.Add(thread); + else + threadNamesThatAreNoLongerAlive.Add(thread.Name); + } + + // Perform Cleanup + foreach (string threadName in threadNamesThatAreNoLongerAlive) + { + _StartedNamedThreads[threadName] = null; + _StartedNamedThreads.Remove(threadName); + } + + return threads.ToArray(); + } + + #endregion + } +} diff --git a/Thread/TTimer.cs b/Thread/TTimer.cs new file mode 100644 index 0000000..8eb592f --- /dev/null +++ b/Thread/TTimer.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Timers; +using System.Windows.Threading; +using WinThread = System.Threading; + +namespace Yaulw.Thread +{ + /// + /// Wrapper class around Timer Object * Multi-Threated * + /// + public class TTimer : IDisposable + { + #region Private Members + + private Timer _Timer = new Timer(); + private bool _disposed = false; + + #endregion + + #region Construction + + /// + /// Creates a new Multi-threaded System.Timer + /// + /// Event Handler for Timer + /// Interval in Miliseconds + /// True to start the timer upon creation, false otherwise + /// A Timer Object, which should be Disposed by Caller + public TTimer(ElapsedEventHandler ElapsedHandler, int IntervalMiliseconds = 1000, bool StartEnabled = false) + { + if (ElapsedHandler != null) + { + _Timer = new System.Timers.Timer(); + _Timer.Elapsed += ElapsedHandler; + + // Set the Interval / start + _Timer.Interval = IntervalMiliseconds; + _Timer.Enabled = StartEnabled; + if (StartEnabled) + _Timer.Start(); + + // Keep the timer alive + GC.KeepAlive(_Timer); + } + } + + /// + /// Finalizer + /// + ~TTimer() + { + Dispose(true); + } + + #endregion + + #region Public Methods + + /// + /// Manually Start the Timer + /// + public void Start() + { + Stop(); // First Stop(), an existing Timer + _Timer.Enabled = true; + _Timer.Start(); + } + + /// + /// Manually Start the Timer at a new Interval + /// + /// Interval in Miliseconds + public void Start(uint IntervalMiliseconds) + { + Stop(); // First Stop(), an existing Timer + _Timer.Interval = IntervalMiliseconds; + _Timer.Enabled = true; + _Timer.Start(); + } + + /// + /// Manually Start the Timer at a new Interval + /// + /// Interval as a TimeSpan + public void Start(TimeSpan tsInterval) + { + Stop(); // First Stop(), an existing Timer + _Timer.Interval = tsInterval.TotalMilliseconds; + _Timer.Enabled = true; + _Timer.Start(); + } + + /// + /// Manually Stop the Timer + /// + public void Stop() + { + _Timer.Enabled = false; + _Timer.Stop(); + } + + #endregion + + #region IDisposable Members + + /// + /// Dispose the Timer Object + /// + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer + GC.SuppressFinalize(this); + } + + /// + /// Dispose the Timer Object + /// + /// true, if called from within + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if (_Timer != null) + _Timer.Dispose(); + } + + // Indicate that the instance has been disposed. + _Timer = null; + _disposed = true; + } + } + + #endregion + } +} diff --git a/Thread/TTimerDisp.cs b/Thread/TTimerDisp.cs new file mode 100644 index 0000000..9e6ad5b --- /dev/null +++ b/Thread/TTimerDisp.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Timers; +using System.Windows.Threading; +using WinThread = System.Threading; + +namespace Yaulw.Thread +{ + /// + /// Wrapper class around Timer Object * Uses Dispatcher * + /// + public class TTimerDisp : IDisposable + { + #region Private Members + + private Timer _Timer = new Timer(); + private bool _disposed = false; + private Dispatcher _Dispatcher = null; + private ElapsedEventHandler _DispatchedElapsedEvent = null; + + #endregion + + #region Construction + + /// + /// Creates a new Multi-threaded System.Timer that uses the Dispatcher to Fire the Event + /// + /// Event Handler for Timer + /// Interval in Miliseconds + /// True to start the timer upon creation, false otherwise + /// A Timer Object, which should be Disposed by Caller + public TTimerDisp(ElapsedEventHandler ElapsedHandler, int IntervalMiliseconds = 1000, bool StartEnabled = false) + { + if (ElapsedHandler != null) + { + _Timer = new System.Timers.Timer(); + + // The Primary Dispatcher thread is the thread that called us + _Dispatcher = Dispatcher.CurrentDispatcher; + _DispatchedElapsedEvent = ElapsedHandler; + _Timer.Elapsed += new ElapsedEventHandler(_Timer_Elapsed); + + // Set the Interval / start + _Timer.Interval = IntervalMiliseconds; + _Timer.Enabled = StartEnabled; + if (StartEnabled) + _Timer.Start(); + + // Keep the timer alive + GC.KeepAlive(_Timer); + } + } + + /// + /// Finalizer + /// + ~TTimerDisp() + { + Dispose(true); + } + + #endregion + + #region Private Helpers + + /// + /// For Dispatching the Event to the Primary Dispatcher Thread + /// + private void _Timer_Elapsed(object sender, ElapsedEventArgs e) + { + object[] param_s = new object[] { sender, e }; + _Dispatcher.Invoke((ElapsedEventHandler)_DispatchedElapsedEvent, param_s); + } + + #endregion + + #region Public Methods + + /// + /// Manually Start the Timer + /// + public void Start() + { + Stop(); // First Stop(), an existing Timer + _Timer.Enabled = true; + _Timer.Start(); + } + + /// + /// Manually Start the Timer at a new Interval + /// + /// Interval in Miliseconds + public void Start(uint IntervalMiliseconds) + { + Stop(); // First Stop(), an existing Timer + _Timer.Interval = IntervalMiliseconds; + _Timer.Enabled = true; + _Timer.Start(); + } + + /// + /// Manually Start the Timer at a new Interval + /// + /// Interval as a TimeSpan + public void Start(TimeSpan tsInterval) + { + Stop(); // First Stop(), an existing Timer + _Timer.Interval = tsInterval.TotalMilliseconds; + _Timer.Enabled = true; + _Timer.Start(); + } + + /// + /// Manually Stop the Timer + /// + public void Stop() + { + _Timer.Enabled = false; + _Timer.Stop(); + } + + #endregion + + #region IDisposable Members + + /// + /// Dispose the Timer Object + /// + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer + GC.SuppressFinalize(this); + } + + /// + /// Dispose the Timer Object + /// + /// true, if called from within + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if (_Timer != null) + _Timer.Dispose(); + } + + // Indicate that the instance has been disposed. + _Timer = null; + _disposed = true; + } + } + + #endregion + } +} diff --git a/Tools/Convert.cs b/Tools/Convert.cs new file mode 100644 index 0000000..278b3d5 --- /dev/null +++ b/Tools/Convert.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Yaulw.Tools +{ + /// + /// Common Conversion Operations + /// + public static class Convert + { + /// + /// Convert a a String to a UTF Byte Array + /// + /// string to convert + /// a Byte Array from a String + public static byte[] StrToByteArrayUTF(string str) + { + if (!String.IsNullOrEmpty(str)) + { + System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding(); + return encoding.GetBytes(str); + } + return null; + } + + /// + /// Convert a a String to an ASCII Byte Array + /// + /// string to convert + /// a Byte Array from a String + public static byte[] StrToByteArrayAscii(string str) + { + if (!String.IsNullOrEmpty(str)) + { + System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding(); + return encoding.GetBytes(str); + } + return null; + } + + + } +} diff --git a/Tools/EnumTool.cs b/Tools/EnumTool.cs new file mode 100644 index 0000000..e1b65a8 --- /dev/null +++ b/Tools/EnumTool.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Yaulw.Tools +{ + /// + /// Common Operations on Enumerations + /// + public static class EnumTool + { + #region Common Operations + + /// + /// Turns an Enum to a String (Replacing _ with ' ') + /// + /// an Enum which could possibly contain an '_' + /// a ' ' spaced String of Enum + public static string Enum_ToString(Enum _enum) + { + return _enum.ToString().Replace('_', ' '); + } + + /// + /// Using the '__' in the Enum Name allows to embed Categories in the Name + /// + /// an Enum with '__' + /// The String Value before the '__', also replacing '_' with ' ' + /// The String Value after the '__', also replacing '_' with ' ' + public static void Enum_ToCategoryNKey(Enum _enum, out string Category, out string Key) + { + Category = String.Empty; + Key = String.Empty; + + if (!Enum_HasCategoryNKey(_enum)) + throw new ArgumentException("_enum must contain a __ with category and key values"); + + string _enumStr = _enum.ToString(); + int nIndex = _enumStr.IndexOf("__"); + + // Assign out the Retrived Values + Category = _enumStr.Substring(0, nIndex).Replace('_', ' '); + Key = _enumStr.Substring(nIndex + 2).Replace('_', ' '); + } + + #endregion + + #region HasCategoryNKey + + /// + /// Determines if the Passed in Enum has both a Category and Key + /// + /// an Enum with '__' + /// true if '__' is present with both a category and key value + public static bool Enum_HasCategoryNKey(Enum _enum) + { + // Check to see there is only one __ + string _enumStr = _enum.ToString(); + int nIndexFirst = _enumStr.IndexOf("__"); + int nIndexLast = _enumStr.LastIndexOf("__"); + if (nIndexFirst == nIndexLast) + { + // Check to see that there are values infront and behind the __ + if (nIndexFirst != 0 && (nIndexLast < (_enumStr.Length - 1))) + return true; + } + return false; + } + + /// + /// Determines if all the Enums of the Passed in Enum Type have both a Category and Key + /// + /// an Enum Type that has all enums with '__' + /// true if '__' is present with both a category and key value on all enums + public static bool EnumType_HasCategoryNKey(Type _enumType) + { + if (_enumType.IsEnum) + { + foreach (string enumName in Enum.GetNames(_enumType)) + { + bool bIsValid = Enum_HasCategoryNKey((Enum)Enum.Parse(_enumType, enumName)); + if (!bIsValid) + return false; + } + return true; + } + return false; + } + + #endregion + + #region Category N' Key Traversals + + /// + /// Retrieves all the Categories of an Enum + /// + /// an Enum Type that has all enums with '__' + /// An Array of all unique Categories, '_' replaced with ' ', or null if error occured + public static string[] EnumType_GetCategories(Type _enumType) + { + if (_enumType.IsEnum && EnumType_HasCategoryNKey(_enumType)) + { + List retVal = new List(); + foreach (string enumName in Enum.GetNames(_enumType)) + { + string Category; + string Key; + Enum_ToCategoryNKey((Enum)Enum.Parse(_enumType, enumName), out Category, out Key); + if(!retVal.Contains(Category)) + retVal.Add(Category); + } + return retVal.ToArray(); + } + return null; + } + + /// + /// Retrieves all the Keys of an Enum for a specified Category + /// + /// an Enum Type that has all enums with '__' + /// An Array of all unique Keys, '_' replaced with ' ', or null if error occured + public static string[] EnumType_GetKeys(Type _enumType, string Category) + { + if (_enumType.IsEnum && EnumType_HasCategoryNKey(_enumType)) + { + List retVal = new List(); + foreach (string enumName in Enum.GetNames(_enumType)) + { + string CategoryTemp; + string Key; + Enum_ToCategoryNKey((Enum)Enum.Parse(_enumType, enumName), out CategoryTemp, out Key); + if (String.Compare(Category, CategoryTemp, true) == 0) + retVal.Add(Key); + } + return retVal.ToArray(); + } + return null; + } + + #endregion + } +} diff --git a/Tools/ObjTool.cs b/Tools/ObjTool.cs new file mode 100644 index 0000000..60ee1be --- /dev/null +++ b/Tools/ObjTool.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Yaulw.Tools +{ + /// + /// Useful Utilities around objects + /// + public static class ObjTool + { + /// + /// Returns true if passed in object is valid and is not empty + /// + /// an object to validate + /// true if valid, false otherwise + public static bool IsNotNullAndNotEmpty(object oToValidate) + { + if ((oToValidate != null) && (oToValidate.ToString() != String.Empty)) + return true; + else + return false; + } + + /// + /// Use this to just check if the object can be converted to a string + /// via ConvertObjToString(). If false, this is most likely an object type that shouldn't be converted to a string. + /// + /// Can be Any type + /// an object of type T to check if String Convertible + /// true, if the object passed in a String Convertible type like string, bool, int32, double, decimal, etc..., false otherwise + public static bool IsOfTypeConvertibleToString(T objToCheckConvert) + { + bool retVal = false; + T objToConvert = objToCheckConvert; + + // Check if String Convertible + if (objToConvert is System.Char) + { + retVal = true; + } + else if (objToConvert is System.String) + { + retVal = true; + } + else if (objToConvert is System.Decimal) + { + retVal = true; + } + else if (objToConvert is System.Int32) + { + retVal = true; + } + else if (objToConvert is System.Int64) + { + retVal = true; + } + else if (objToConvert is System.Single) + { + retVal = true; + } + else if (objToConvert is System.Double) + { + retVal = true; + } + else if (objToConvert is System.Boolean) + { + retVal = true; + } + else if (objToConvert is System.DateTime) + { + retVal = true; + } +#if NET4 + else if (objToConvert is System.Guid) // Only works in .Net 4.0 + { + retVal = true; + } +#endif + else if (objToConvert is System.IntPtr) + { + retVal = true; + } + else if (objToConvert is System.UInt32) + { + retVal = true; + } + else if (objToConvert is System.UInt64) + { + retVal = true; + } + + return retVal; + } + + /// + /// Convert a string to an Object of Type T + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// String Value to Convert + /// an converted Object of Type t, or T Default if error occured + public static T ConvertStringToObj(string strToConvert) + { + + // Create a Default Type T + T retVal = default(T); + + try + { + #region Conversion + + if (retVal is System.Char) + { + retVal = (T)((object)strToConvert[0]); + } + else if (retVal is System.String) + { + retVal = (T)((object)strToConvert); + } + else if (retVal is System.Decimal) + { + retVal = (T)((object)Decimal.Parse(strToConvert)); + } + else if (retVal is System.Int32) + { + retVal = (T)((object)Int32.Parse(strToConvert)); + } + else if (retVal is System.Int64) + { + retVal = (T)((object)Int64.Parse(strToConvert)); + } + else if (retVal is System.Single) + { + retVal = (T)((object)Single.Parse(strToConvert)); + } + else if (retVal is System.Double) + { + retVal = (T)((object)Double.Parse(strToConvert)); + } + else if (retVal is System.Boolean) + { + retVal = (T)((object)Boolean.Parse(strToConvert)); + } + else if (retVal is System.DateTime) + { + retVal = (T)((object)DateTime.Parse(strToConvert)); + } +#if NET4 + else if (retVal is System.Guid) // Works in .Net 4.0 and up + { + retVal = (T)((object)Guid.Parse(strToConvert)); + } +#endif + else if (retVal is System.IntPtr) + { + if (IntPtr.Size <= 4) + { + Int32 i = Int32.Parse(strToConvert); + retVal = (T)((object)new IntPtr(i)); + } + else + { + Int64 i = Int64.Parse(strToConvert); + retVal = (T)((object)new IntPtr(i)); + } + } + else if (retVal is System.UInt32) + { + retVal = (T)((object)UInt32.Parse(strToConvert)); + } + else if (retVal is System.UInt64) + { + retVal = (T)((object)UInt64.Parse(strToConvert)); + } + else + { + retVal = (T)((object)(strToConvert)); + } + + #endregion + } + catch (Exception e) { string Message = e.Message; /* ignore */ } + + // return T + return retVal; + } + + /// + /// Convert an object of type t to a String + /// + /// Should be a System Type like string, bool, int32, double, decimal, etc... + /// Object Value to Convert + /// a string that contains the value of the Object + public static string ConvertObjToString(T objToConvert) + { + String retVal = String.Empty; + try + { + #region Conversion + + if (objToConvert is System.Char) + { + retVal = ((Char)((object)objToConvert)).ToString(); + } + else if (objToConvert is System.String) + { + retVal = ((String)((object)objToConvert)).ToString(); + } + else if (objToConvert is System.Decimal) + { + retVal = ((Decimal)((object)objToConvert)).ToString(); + } + else if (objToConvert is System.Int32) + { + retVal = ((Int32)((object)objToConvert)).ToString(); + } + else if (objToConvert is System.Int64) + { + retVal = ((Int64)((object)objToConvert)).ToString(); + } + else if (objToConvert is System.Single) + { + retVal = ((Single)((object)objToConvert)).ToString(); + } + else if (objToConvert is System.Double) + { + retVal = ((Double)((object)objToConvert)).ToString(); + } + else if (objToConvert is System.Boolean) + { + retVal = ((Boolean)((object)objToConvert)).ToString(); + } + else if (objToConvert is System.DateTime) + { + retVal = ((DateTime)((object)objToConvert)).ToString(); + } +#if NET4 + else if (objToConvert is System.Guid) + { + retVal = ((Guid)((object)objToConvert)).ToString(); + } +#endif + else if (objToConvert is System.IntPtr) + { + retVal = ((IntPtr)((object)objToConvert)).ToString(); + } + else if (objToConvert is System.UInt32) + { + retVal = ((UInt32)((object)objToConvert)).ToString(); + } + else if (objToConvert is System.UInt64) + { + retVal = ((UInt64)((object)objToConvert)).ToString(); + } + else + { + retVal = ((object)objToConvert).ToString(); + } + + #endregion + } + catch (Exception) { /* ignore */ } + + // return T + return retVal; + } + } +} diff --git a/Tools/PathNaming.cs b/Tools/PathNaming.cs new file mode 100644 index 0000000..aadb31c --- /dev/null +++ b/Tools/PathNaming.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace Yaulw.Tools +{ + /// + /// Tools For File/Directory Paths + /// + public static class PathNaming + { + /// + /// Get the Root Path for a given path string + /// + /// path + /// the root folder, with '\' + public static string GetPathRoot(string path) + { + if(!String.IsNullOrEmpty(path)) + return PathEndsWithSlash(System.IO.Path.GetPathRoot(path)); + return ""; + } + + /// + /// Does Path contain file information + /// + /// returns true if the specified path information contains file information + public static bool PathContainsFile(string path) + { + if (!String.IsNullOrEmpty(path) && (path.LastIndexOf('.') > path.LastIndexOf('\\'))) + return true; + return false; + } + + /// + /// Makes sure that the specified path begins with a '\' + /// + /// a path string that begins with \ + public static string PathBeginsWithSlash(string path) + { + if (!String.IsNullOrEmpty(path) && (path[0] != '\\')) + return '\\' + path; + return path; + } + + /// + /// Makes sure that the specified path does NOT begin with a '\' + /// + /// a path string that does NOT begin with \ + public static string PathBeginsWithNoSlash(string path) + { + if (!String.IsNullOrEmpty(path) && (path[0] == '\\')) + return path.Substring(1); + return path; + } + + /// + /// Makes sure that the specified path ends with a '\' + /// + /// a path string that ends with \ + public static string PathEndsWithSlash(string path) + { + if (!String.IsNullOrEmpty(path) && (path[path.Length - 1] != '\\')) + return path + '\\'; + return path; + } + + /// + /// Makes sure that the specified path does NOT end with a '\' + /// + /// a path string that does NOT end with \ + public static string PathEndsWithNoSlash(string path) + { + if (!String.IsNullOrEmpty(path) && (path[path.Length - 1] == '\\')) + return path.Substring(0, path.Length - 1); + return path; + } + + /// + /// Returns True if the fileName is valid * Filename chars are More restrictive than Path * should always use this + /// + /// filename to check for + /// true, if valid, false otherwise + public static bool FileNameIsValid(string FileName) + { + if (!String.IsNullOrEmpty(FileName)) + { + // stripe out the filename, if possible, if not, just use what is + // passed into us + string file = Path.GetFileName(FileName); + if (!String.IsNullOrEmpty(file)) + FileName = file; + + char[] invalidChars = Path.GetInvalidFileNameChars(); + foreach (char c in invalidChars) + { + if (FileName.Contains(c)) + return false; + } + return true; + } + return false; + } + + /// + /// Returns True if the Directory Path is valid * Path chars are not as restrictive as File * should always use File Chars (better safe than sorry) + /// Use FileNameIsValid() + /// + /// DirectoryPath to check for + /// true, if valid, false otherwise + public static bool IsDirectoryPathValid(string DirectoryPath) + { + if (!String.IsNullOrEmpty(DirectoryPath)) + { + // stripe the trailing '\\', if possible, if not, just use what is + // passed into us + string path = Path.GetDirectoryName(DirectoryPath); + if (!String.IsNullOrEmpty(path)) + DirectoryPath = path; + + // FileName Char restrictions contain everything we need, + // except for a path we want to allow '\\' + char[] invalidChars = Path.GetInvalidFileNameChars(); + + foreach (char c in invalidChars) + { + // allow '\\' for paths + if (c != '\\' && DirectoryPath.Contains(c)) + return false; + } + return true; + } + return false; + } + + /// + /// return a valid directory path, a path that is invalid and contains invalid + /// characters is automatically converted, (illegal characters are skipped) + /// + /// DirectoryPath to change + /// the 'corrected' path string + public static string MakeDirectoryPathValid(string DirectoryPath) + { + return MakeDirectoryPathValid(DirectoryPath, String.Empty); + } + + /// + /// return a valid directory path, a path that is invalid and contains invalid + /// characters is automatically converted, (illegal characters are skipped) + /// + /// DirectoryPath to change + /// the string to put in place instead of the invalid char found, + /// it itself can't be an invalid char + /// the 'corrected' path string + public static string MakeDirectoryPathValid(string DirectoryPath, string replacementStr) + { + if(!String.IsNullOrEmpty(DirectoryPath)) + { + // stripe the trailing '\\', if possible, if not, just use what is + // passed into us + string path = Path.GetDirectoryName(DirectoryPath); + if (!String.IsNullOrEmpty(path)) + DirectoryPath = path; + + // FileName Char restrictions contain everything we need, + // except for a path we want to allow '\\' + char[] invalidChars = Path.GetInvalidFileNameChars(); + + // Make sure replacementStr is valid also, otherwise it somewhat defeats the purpose + // of this whole function + string strToUseForReplacement = ""; + bool replacementStrIsValid = false; + if (replacementStr != null) + { + replacementStrIsValid = true; + if (replacementStr != "") + { + foreach (char c in replacementStr) + { + invalidChars.Contains(c); + replacementStrIsValid = false; + break; + } + } + } + if (replacementStrIsValid) + strToUseForReplacement = replacementStr; + + // Construct new String Path + StringBuilder sb = new StringBuilder(); + foreach (char c in DirectoryPath) + { + // allow '\\' for paths + if (c != '\\' && invalidChars.Contains(c)) + { + sb.Append(strToUseForReplacement); + } + else + { + sb.Append(c); + } + } + return sb.ToString(); + } + return DirectoryPath; + } + + /// + /// Retrieve the last directory name from a path, can be a UNC path like \\{server}\Directory + /// or a file and system path like C:\{directory}\{directory}, will always return the name of the + /// last directory in the path. if it is not a path, i.e just a name is passed in without '\'s then + /// it will just stripe out all '\' that maybe there and return the name + /// + /// + /// the name of the last directory in the path + public static string RetrieveLastDirectoryNameInPath(string DirectoryPath) + { + if (!String.IsNullOrEmpty(DirectoryPath)) + { + // Find the last folder and return that information + // This is what this function was created for + string dp = Path.GetDirectoryName(DirectoryPath); + int nLast = dp.LastIndexOf('\\'); + if (nLast != -1 && dp.Length > 3) + { + dp = dp.Substring(nLast + 1); + return dp; + } + + // Return what was passed into us, probably doesn't have a '\\', + // but it could be that it just has one. like someone passed in \\{server}, + // then we'll just return the {server}, if someone passes in c:\directory, then, + // we should pass back just the directory + if (DirectoryPath.Length > 3 && DirectoryPath[1] == ':') + { + return DirectoryPath.Substring(3); + } + else + { + // try finding the last \\, again without using GetDirectoryName which + // stripes it, return the last stuff found, otherwise just return what was passed to us + nLast = DirectoryPath.LastIndexOf('\\'); + if (nLast != -1 && DirectoryPath.Length > 3) + { + return DirectoryPath.Substring(nLast + 1); + } + else + { + return DirectoryPath; + } + } + } + return String.Empty; + } + + } +} diff --git a/Tools/StringTool.cs b/Tools/StringTool.cs new file mode 100644 index 0000000..d98da01 --- /dev/null +++ b/Tools/StringTool.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Yaulw.Tools +{ + /// + /// Useful String Operations + /// + public static class StringTool + { + /// + /// Searches for the 'value' occurences that occur in 'str' + /// + /// string to search occurences for + /// string to search in + /// true to ignore case, false otherwise + /// the number of occurences of 'value' in 'str', 0 for none + public static uint NumberOfSpecStringFoundInString(string value, string str, bool bIgnoreCase = false) + { + uint nCount = 0; + if (!String.IsNullOrEmpty(str)) + { + int nIndex = 0; + StringComparison comparison = bIgnoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture; + do + { + nIndex = str.IndexOf(value, nIndex, comparison); + if(nIndex != -1) + ++nCount; + } while (nIndex != -1); + } + return nCount; + } + + /// + /// Check to see if string starts with a letter + /// + /// string to check + /// true if string starts with a letter + public static bool StringStartsWithLetter(string str) + { + if (!String.IsNullOrEmpty(str)) + return char.IsLetter(str[0]); + return false; + } + + /// + /// Check to see if string ends with a letter + /// + /// string to check + /// true if string ends with a letter + public static bool StringEndsWithLetter(string str) + { + if (!String.IsNullOrEmpty(str)) + return char.IsLetter(str[str.Length - 1]); + return false; + } + + /// + /// Returns the last word of a string (string must end with a letter) + /// + /// string to go thru + /// the last word of a string + public static string StringFetchLastWord(string str) + { + if (!String.IsNullOrEmpty(str) && StringEndsWithLetter(str)) + { + // Find the last word + StringBuilder sb = new StringBuilder(); + for (int i = str.Length - 1; i >= 0; --i) + { + if (char.IsLetter(str[i])) + sb.Append(str[i]); + else + break; + } + + // Reverse the String + string strRet = sb.ToString(); +#if NET4 + sb.Clear(); +#else + sb = new StringBuilder(); +#endif + for (int i = strRet.Length - 1; i >=0; --i) + sb.Append(strRet[i]); + + // return the string + return sb.ToString(); + } + return String.Empty; + } + + /// + /// Returns the first word of a string (string must start with a letter) + /// + /// string to go thru + /// the first word of a string + public static string StringFetchFirstWord(string str) + { + if (!String.IsNullOrEmpty(str) && StringStartsWithLetter(str)) + { + // Find the last word + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < str.Length; ++i) + { + if (char.IsLetter(str[i])) + sb.Append(str[i]); + else + break; + } + + // return the string + return sb.ToString(); + } + return String.Empty; + } + + /// + /// Stripes the last word from the referenced string (string must end with a letter) + /// + /// last word stripped from string + public static void StringStripeLastWord(ref string str) + { + if (!String.IsNullOrEmpty(str) && StringEndsWithLetter(str)) + { + // Find the last word + int i = 0; + for (i = str.Length - 1; i >= 0; --i) + { + if (!char.IsLetter(str[i])) + break; + } + + // Return string without last word + str = str.Substring(0, i + 1); + } + } + + /// + /// Stripes the first word from the referenced string (string must start with a letter) + /// + /// first word stripped from string + public static void StringStripeFirstWord(ref string str) + { + if (!String.IsNullOrEmpty(str) && StringStartsWithLetter(str)) + { + // Find the last word + int i = 0; + for (i = 0; i < str.Length; ++i) + { + if (!char.IsLetter(str[i])) + break; + } + + // Return string without first word + str = str.Substring(i); + } + } + + } + +} diff --git a/WPF/KeepHidden.cs b/WPF/KeepHidden.cs new file mode 100644 index 0000000..bdc3290 --- /dev/null +++ b/WPF/KeepHidden.cs @@ -0,0 +1,319 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Threading; +using System.Timers; +using Yaulw.Thread; + +namespace Yaulw.WPF +{ + /// + /// Class used to keep WPF Windows Hidden permanently. + /// This should ideally be called in the Constructor of a window you want to keep hidden. + /// + /// + /// private static KeepHidden _HiddenWindow = null; + /// public HiddenMainWindow() + /// { + /// // * Force this window to remain hidden indefinitly* + /// _HiddenWindow = new KeepHidden(this); + /// } + /// + /// + /// + public class KeepHidden + { + #region Private Statics + + // Static Map of All our Hidden Windows + private static Dictionary s_hWndToWindowMap = new Dictionary(); + private static List s_WindowList = new List(); + + private Window _wpfWindow = null; + private object _lockObj = new Object(); + + #endregion + + #region Public Properties + + /// + /// Get the WPF Object That is bound to this Keep Hidden Instance + /// + public Window wpfWindow { get { return _wpfWindow; } } + + /// + /// Get the HWND Handle to the WPF Object bound to this Keep Hidden Instance + /// + public IntPtr hwndWindow + { + get + { + if (_wpfWindow != null) + { + WindowInteropHelper interop = new WindowInteropHelper(_wpfWindow); + return interop.Handle; + } + return IntPtr.Zero; + } + } + + #endregion + + #region Construction + + /// + /// When Passing in a Wpf Window in this Construction, it will ensure that + /// This Window will remain hidden during it's life span. If the Window is Not Loaded + /// yet, call KeepHidden's Show() method to show it, after construction. + /// + /// a WPF Window Object that you want to keep Hidden + public KeepHidden(Window wpfWindow) + { + if (wpfWindow != null && !wpfWindow.IsLoaded) + { + // Assign the Load Event Handler we care about + wpfWindow.Loaded += new RoutedEventHandler(wpfWindow_Loaded); + } + else if (wpfWindow != null && wpfWindow.IsLoaded) + { + // Call the Loaded Event Handler Directly + wpfWindow_Loaded((object)wpfWindow, null); + } + else + throw new ArgumentException("Please pass valid WPF Window Object"); + + // Make sure we aren't already tracking this window + if (s_WindowList.Contains(wpfWindow)) + throw new ArgumentException("WPF Window Object already Kept Hidden"); + + // Assign remaining stuff + wpfWindow.Closing += new System.ComponentModel.CancelEventHandler(wpfWindow_Closing); + AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit); + _wpfWindow = wpfWindow; + + // Add to WPF Window List + s_WindowList.Add(wpfWindow); + + // Call this upon construction ~Done Way before Show() get's called + HideWPFWindow(_wpfWindow); + } + + #endregion + + #region Public Methods + + /// + /// Call this to execute a Show() call on the WPF Window that you want to Keep Hidden + /// + public void Show() + { + if (_wpfWindow != null && !_wpfWindow.IsLoaded) + { + lock (_lockObj) + { + // Make sure it can't be seen or interacted with + HideWPFWindow(_wpfWindow); + + // Show it + _wpfWindow.Show(); + + // Again... make sure... + HideWPFWindow(_wpfWindow); + + // Hide it immediatly, make sure it can't be interacted with + _wpfWindow.Hide(); + } + } + } + + /// + /// Call this to execute a Show() call on the WPF Window that you want to Keep Hidden + /// ~However, it will delay call the show allowing the hidden window's constructor to finish + /// + /// Milliseconds to delay show the HiddenWindow (at least 250 recommended) + public void Show(uint nDelayInMilliseconds) + { + // Calling Show() directly in the Constructor causes a .Net Run-time Crash + // upon first launch only. Calling it .5 seconds later does the trick of avoiding that error, + // because by then the object is fully constructed + ElapsedEventHandler DelayShowHiddenWindow = delegate(object sender, ElapsedEventArgs e) + { + Timer timer = (Timer)sender; + timer.Stop(); + + // * Show the Hidden Window * + Show(); + + timer.Dispose(); + timer = null; + }; + TTimerDisp timerDelayShowHiddenWindow = new TTimerDisp(DelayShowHiddenWindow, (int) nDelayInMilliseconds, true); + } + + /// + /// Call this to execute a Close() call on the WPF Window that you previously had hidden + /// ~Should only call this when you are done with the Keep Hidden Object + /// + public void Close() + { + if (_wpfWindow != null && _wpfWindow.IsLoaded) + { + _wpfWindow.Close(); + _wpfWindow = null; + } + } + + #endregion + + #region Public Static Methods + + /// + /// Call this function to determine if the passed in Window is a Hidden Window Object * Tracked by KeepHidden * + /// + /// pass in a wpf window object to check + /// + public static bool IsHiddenWindow(Window _wpfWindow) + { + if (_wpfWindow != null) + { + bool bIsFound = s_WindowList.Contains(_wpfWindow); + return bIsFound; + } + return false; + } + + #endregion + + #region Private Keep Hidden Event Handlers + + /// + /// Called by the WPFWindow's OnLoad Event + /// + private void wpfWindow_Loaded(object sender, RoutedEventArgs e) + { + lock (_lockObj) + { + Window wpfWindow = (Window)sender; + HideWPFWindow(wpfWindow); + + // Add Window Source Message Hook * We'll track the hWnd for the Message Hook + // vie s_hWndToWindowMap + WindowInteropHelper interop = new WindowInteropHelper(wpfWindow); + s_hWndToWindowMap[interop.Handle] = wpfWindow; + HwndSource source = HwndSource.FromHwnd(interop.Handle); + source.AddHook(new HwndSourceHook(MessageHook)); + } + } + + /// + /// Called by the WPFWindow's Closing Event + /// + private void wpfWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + lock (_lockObj) + { + // Delete from the hWndMap, so Message Hook + // Stops processing messages + Window wpfWindow = (Window)sender; + if (s_hWndToWindowMap.ContainsValue(wpfWindow)) + { + foreach (IntPtr hWnd in s_hWndToWindowMap.Keys) + { + if (s_hWndToWindowMap[hWnd] == wpfWindow) + { + s_hWndToWindowMap.Remove(hWnd); + break; + } + } + } + + // * Imp * Delete internal wpfWindow Reference, + // but DON'T Delete from the s_WindowList. Caller could still call IsHiddenWindow(), + // and we MUST return true if it is + _wpfWindow = null; + } + } + + /// + /// Called by the Process Exiting * We want this to happen last * + /// This allows all unload events for each window to happen first + /// + void CurrentDomain_ProcessExit(object sender, EventArgs e) + { + // Make sure to clear all windows + s_hWndToWindowMap.Clear(); + + // Make sure to clear all windows + s_WindowList.Clear(); + } + + /// + /// This Message Hook is for the WpfWindow to absolutely ensure that it remains Hidden. + /// We want to make sure that it is never displayed *It can occur that explorer.exe can show it, without this hook* + /// ~i.e. when explorer.exe crashes, and get's restarted, it can cause inconsitencies + /// + private IntPtr MessageHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + // Get the Proper WPF Object and make sure it is Hidden + if (s_hWndToWindowMap.ContainsKey(hwnd)) + { + Window wpfWindow = s_hWndToWindowMap[hwnd]; + if (wpfWindow != null) + HideWPFWindow(wpfWindow); + } + handled = false; + return System.IntPtr.Zero; + } + + #endregion + + #region Private Hide Window Functionality + + /// + /// * Core * 'Hiding' Function. Makes sure that the Window is and + /// stays hidden. + /// + /// + /// Height="2" Width="2" AllowsTransparency="True" Background="{x:Null}" WindowStyle="None" WindowStartupLocation="Manual" WindowState="Normal" ResizeMode="NoResize" Foreground="{x:Null}" ShowInTaskbar="False" ShowActivated="False" Left="0" Top="0" MaxWidth="2" MaxHeight="2" MinHeight="2" MinWidth="2" Visibility="Hidden" Opacity="0" IsHitTestVisible="False" IsTabStop="False" Focusable="False" IsEnabled="False" + /// + /// a WPF window object + private void HideWPFWindow(Window wpfWindow) + { + // Force a tiny Window in top-left corner + wpfWindow.Width = 2; + wpfWindow.Height = 2; + wpfWindow.MinWidth = 2; + wpfWindow.MinHeight = 2; + wpfWindow.MaxWidth = 2; + wpfWindow.MaxHeight = 2; + wpfWindow.Top = 0; + wpfWindow.Left = 0; + + // Force transparency + if (!wpfWindow.IsLoaded) // must not be already loaded + wpfWindow.AllowsTransparency = true; + wpfWindow.Background = null; + wpfWindow.Foreground = null; + + // Force no user interaction + wpfWindow.WindowStartupLocation = WindowStartupLocation.Manual; + wpfWindow.WindowState = WindowState.Normal; + wpfWindow.ResizeMode = ResizeMode.NoResize; + wpfWindow.ShowInTaskbar = false; + wpfWindow.ShowActivated = false; + wpfWindow.Visibility = Visibility.Hidden; + wpfWindow.IsHitTestVisible = false; + wpfWindow.IsTabStop = false; + wpfWindow.Focusable = false; + + // Force Opacity + wpfWindow.WindowStyle = WindowStyle.None; + wpfWindow.Opacity = 0.0; + } + + #endregion + } +} diff --git a/WPF/Snippets.cs b/WPF/Snippets.cs new file mode 100644 index 0000000..29c9c38 --- /dev/null +++ b/WPF/Snippets.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Media.Imaging; +using System.Windows.Media; +using System.Windows.Interop; + +namespace Yaulw.WPF +{ + /// + /// Useful WPF Snippets + /// + static public class Snippets + { + /// + /// Useful to create a simple wrapping tooltip for a wpf control + /// + /// + /// Set to 0 to leave default, else set to 0> to define max height + /// Set to 0 to leave default, else set to 0> to define max width + /// a ToolTip Object to assign to a control + public static ToolTip CreateNewStringToolTip(string strToolTipContent, int height, int width) + { + // Create the TextBlock + TextBlock tx = new TextBlock(); + if (height > 0) + tx.Height = height; + if (width > 0) + tx.Width = width; + tx.TextWrapping = TextWrapping.Wrap; + tx.Text = strToolTipContent; + + // Create the ToolTip with the TextBlock inside + ToolTip tt = new ToolTip(); + tt.Content = strToolTipContent; + return tt; + } + + /// + /// Useful if you want to know if the Mouse cursor is over the specified Wpf Window + /// (Uses windowsforms to do the calculations) + /// + /// true if the mouse cursor is over the specified form window, false otherwise + public static bool IsMouseCursorOverWPFormWindow(Window wpfWindow) + { + if (wpfWindow != null) + { + // Get the Point location of the mouse cursor + System.Drawing.Point point = System.Windows.Forms.Cursor.Position; + + // Get the rect for the Navigation Form + Point wpfFormLeftTopPoint = new Point(wpfWindow.Left, wpfWindow.Top); + Size wpfFormSize = new Size(wpfWindow.Width, wpfWindow.Height); + Rect rect = new Rect(wpfFormLeftTopPoint, wpfFormSize); + + // Return if the Mouse is over the Navigation Form + return (rect.Contains((double)point.X, (double)point.Y)); + } + return false; + } + + /// + /// For Dynamically Creating Text ComboBox Items + /// + /// Text Content to Display + /// object to tag to the menu item + /// + public static ComboBoxItem CreateAComboBoxItem(string strContent, object tag) + { + if (!String.IsNullOrEmpty(strContent) && (tag != null)) + { + ComboBoxItem item = new ComboBoxItem(); + item.Content = strContent; + item.Tag = tag; + return item; + } + return null; + } + + /// + /// For dynamically creating Image sources, this function helps you to create an image out of + /// a Resource Uri. You can then assign this image to a 'Source' property on an image wpf object + /// + /// a valid relative Resource Uri String to an image + /// the output image's desired height + /// the output image's desired width + /// + public static Image CreateWPFImageFromRelativeResourceUri(string strRelativeResourceUri, int ImageHeight, int ImageWidth) + { + if (!String.IsNullOrEmpty(strRelativeResourceUri)) + { + Image myImage = new Image(); + BitmapImage image = new BitmapImage(); + image.BeginInit(); + image.UriSource = new Uri(strRelativeResourceUri, UriKind.Relative); + image.EndInit(); + myImage.Stretch = Stretch.Fill; + myImage.Height = ImageHeight; + myImage.Width = ImageWidth; + myImage.Source = image; + return myImage; + } + return null; + } + + /// + /// Uses the InteropHelper to get the Handle of a WPF Window + /// + /// a WPF Window + /// an hWnd for the specified WPF Window + public static IntPtr GetHandleForWPFWindow(Window window) + { + WindowInteropHelper InteropHelper = new WindowInteropHelper(window); + if (InteropHelper.Handle != IntPtr.Zero) + return InteropHelper.Handle; +#if NET4 + else + return InteropHelper.EnsureHandle(); +#else + else + return IntPtr.Zero; +#endif + } + + /// + /// Uses the InteropHelper to set the Owner Property of a WPF Window + /// + /// a WPF Window + /// Owner hWnd Handle to set for WPF Window + public static void SetOwnerForWPFWindow(Window window, IntPtr hOwnerWnd) + { + WindowInteropHelper InteropHelper = new WindowInteropHelper(window); + InteropHelper.Owner = hOwnerWnd; + } + + /// + /// Adds the Specified Message Hook the the specified WPF Window. + /// Ensures that the WPF Window exists (must be loaded with valid hWnd). + /// + /// a WPF Window + /// a message hook function + public static void SetMessageHookForWPFWindow(Window window, HwndSourceHook hook) + { + WindowInteropHelper interop = new WindowInteropHelper(window); +#if NET4 + HwndSource source = HwndSource.FromHwnd(interop.EnsureHandle()); +#else + HwndSource source = HwndSource.FromHwnd(interop.Handle); +#endif + source.AddHook(hook); + } + } +} diff --git a/WPF/WPFWindowManager.cs b/WPF/WPFWindowManager.cs new file mode 100644 index 0000000..8148155 --- /dev/null +++ b/WPF/WPFWindowManager.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Threading; +using Yaulw.Thread; +using System.Windows; +using Yaulw.Other; +using System.Collections; +using System.Windows.Interop; + +namespace Yaulw.WPF +{ + + /// + /// Responsible for Managing WPFWindows + /// + public class WPFWindowManager : WindowManager + { + #region Construction + + /// + /// WPFWindow Manager Contructor + /// + /// Thread Dispatcher Object + /// Window Types this WindowManager is managing + /// true, to load performance enabled windows upon construction + public WPFWindowManager(Dispatcher dispatcher, WindowType[] windowTypes, bool PerformanceLoad) + : base(dispatcher, windowTypes, PerformanceLoad) + { + // Here i can load Show/hide all windows that are performance loaded * To further load stuff WPF related * + if (PerformanceLoad) + { + foreach (WindowType type in windowTypes) + { + List objects = GetObjects(type); + if (objects.Count == 1 && type.IsPerformanceLoaded) + { + // Show/Hide Window! + } + } + } + } + + #endregion + + #region Public Overriden Methods + + /// + /// + /// + /// Window to run action on + /// Window Attributes to use + /// object to pass into Window + /// the tag object from the window + public override object ShowWindow(Enum windowKey, WindowAttributes attributes, object tag) + { + DelegateCollection.Obj_Func func = delegate() + { + object o = CreateInstance(windowKey); + if (AddObject(windowKey, o)) + { + Window wpfWin = (Window)o; + wpfWin.Tag = tag; + + // Set Attributes + SetAttributesOnWindow(wpfWin, attributes); + + // Show + wpfWin.Show(); + object retVal = wpfWin.Tag; + wpfWin = null; + return retVal; + } + return null; + }; + + if (Dispatcher.Thread == System.Threading.Thread.CurrentThread) + { + return func(); + } + else + { + object[] parameters = new object[] { windowKey, attributes, tag }; + return Dispatcher.Invoke((ShowDelegate)ShowWindow, DispatcherPriority.Normal, parameters); + } + } + + /// + /// Show the WPF Window as a Dialog + /// + /// Window to run action on + /// Window Attributes to use + /// object to pass into Window + /// the tag object from the window + public override object ShowDialog(Enum windowKey, WindowAttributes attributes, object tag) + { + DelegateCollection.Obj_Func func = delegate() + { + object o = CreateInstance(windowKey); + if (AddObject(windowKey, o)) + { + Window wpfWin = (Window)o; + wpfWin.Tag = tag; + + // Set Attributes + SetAttributesOnWindow(wpfWin, attributes); + + // ShowDialog() + wpfWin.ShowDialog(); + object retVal = wpfWin.Tag; + DeleteObject(windowKey); + wpfWin = null; + return retVal; + } + return null; + }; + + if (Dispatcher.Thread == System.Threading.Thread.CurrentThread) + { + return func(); + } + else + { + object[] parameters = new object[] { windowKey, attributes, tag }; + return Dispatcher.Invoke((ShowDelegate)ShowDialog, DispatcherPriority.Normal, parameters); + } + } + + /// + /// Run any generic action on a window + /// + /// Window to run action on + /// action to perform on the windo + public override bool RunAction(Enum windowKey, DelegateCollection.Bool_Param1_Window_Func action) + { + if (Dispatcher.Thread == System.Threading.Thread.CurrentThread) + { + List objects = GetObjects(windowKey); + object o = null; + if (objects != null) + o = objects.Last(); + if (o != null && o is Window && action != null) + return action((Window)o); + else + return action(null); + } + else + { + object[] parameters = new object[] { windowKey, action }; + return (bool)Dispatcher.Invoke((ActionDelegate)RunAction, DispatcherPriority.Normal, parameters); + } + } + + /// + /// Hide the Specified Window + /// + /// Window to run action on + /// true, if successful, false otherwise + public override bool HideWindow(Enum windowKey) + { + DelegateCollection.Bool_Param1_Window_Func func = delegate(Window wpfWin) + { + wpfWin.Hide(); + return true; + }; + return RunAction(windowKey, func); + } + + /// + /// Close the Specified Window + /// + /// Window to run action on + /// true, if successful, false otherwise + public override bool CloseWindow(Enum windowKey) + { + DelegateCollection.Bool_Param1_Window_Func func = delegate(Window wpfWin) + { + DeleteObject(windowKey); + return true; + }; + return RunAction(windowKey, func); + } + + /// + /// Dispose of this manager Properly + /// + public override void Dispose() + { + // We must call the base's ClearAllObjects() + DelegateCollection.Bool_Param1_Window_Func func = delegate(Window wpfWin) + { + ClearAllObjects(); + return true; + }; + RunAction(null, func); + } + + #endregion + + #region Private Helpers + + /// + /// Set the Window Attributes as specifed by the WindowAttributes type + /// + /// Attributes to set + /// Window to set them on + private void SetAttributesOnWindow(Window wpfWin, WindowAttributes attributes) + { + // Set Attributes + if (attributes != null && wpfWin != null) + { + WindowInteropHelper InteropHelper = new WindowInteropHelper(wpfWin); + if (attributes.hWndOwner != IntPtr.Zero) + InteropHelper.Owner = (IntPtr)attributes.hWndOwner; + if (attributes.Height != 0) + wpfWin.Height = attributes.Height; + if (attributes.Width != 0) + wpfWin.Width = attributes.Width; + if (attributes.Left != int.MinValue) + wpfWin.Left = attributes.Left; + if (attributes.Top != int.MinValue) + wpfWin.Top = attributes.Top; + if (!String.IsNullOrEmpty(attributes.Title)) + wpfWin.Title = attributes.Title; + } + } + + #endregion + } + +} diff --git a/Web/HTTP.cs b/Web/HTTP.cs new file mode 100644 index 0000000..7a2bf0e --- /dev/null +++ b/Web/HTTP.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using System.IO; + +namespace Yaulw.Web +{ + public class HTTP + { + + + /// + /// urlCreated.Text = Url; + /// var toPost = Url.Split('?'); + /// var result = HttpPost(toPost[0], toPost[1]); + /// + /// + /// + /// String.Empty if error occured, non-empty string otherwise + string HttpPost(string uri, string parameters) + { + var cookies = new CookieContainer(); + var webRequest = (HttpWebRequest)WebRequest.Create(uri); + + webRequest.CookieContainer = cookies; + webRequest.ContentType = "application/x-www-form-urlencoded"; + webRequest.Method = "POST"; + byte[] bytes = Encoding.ASCII.GetBytes(parameters); + Stream os = null; + try + { + webRequest.ContentLength = bytes.Length; //Count bytes to send + os = webRequest.GetRequestStream(); + os.Write(bytes, 0, bytes.Length); //Send it + } + catch (Exception) { /* ignore */ } + finally + { + if (os != null) + { + os.Close(); + } + } + + try + { + WebResponse webResponse = webRequest.GetResponse(); + if (webResponse == null) + { return null; } + StreamReader sr = new StreamReader(webResponse.GetResponseStream()); + return sr.ReadToEnd().Trim(); + } + catch (Exception) { /* ignore */ } + return String.Empty; + } + } +} diff --git a/Web/WebClientWithTimeout.cs b/Web/WebClientWithTimeout.cs new file mode 100644 index 0000000..053339f --- /dev/null +++ b/Web/WebClientWithTimeout.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; + +namespace Yaulw.Web +{ + /// + /// + /// + public class WebClientWithTimeout : WebClient + { + /// + /// + /// + public int Timeout { get; private set; } + + /// + /// + /// + /// + public WebClientWithTimeout(int timeout) + { + this.Timeout = timeout; + } + + /// + /// + /// + /// + /// + protected override WebRequest GetWebRequest(Uri address) + { + var result = base.GetWebRequest(address); + result.Timeout = this.Timeout; + return result; + } + + + + } +} diff --git a/Win32/Advapi32.cs b/Win32/Advapi32.cs new file mode 100644 index 0000000..d52d099 --- /dev/null +++ b/Win32/Advapi32.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + + +namespace Yaulw.Win32 +{ + /// + /// Advapi.dll Entry Points * http://pinvoke.net/ * + /// + public static class Advapi32 + { + /// + /// Retrieves the current status of the specified service based on the specified information level + /// + /// A handle to the service + /// The service attributes to be returned + /// A pointer to the buffer that receives the status information + /// he size of the buffer pointed to by the lpBuffer + /// A pointer to a variable that receives the number of bytes needed to store all status information + /// If the function succeeds, the return value is nonzero + [DllImport("Advapi32.dll")] + extern public static bool QueryServiceStatusEx(IntPtr hService, int InfoLevel, ref Structures.SERVICE_STATUS_PROCESS lpBuffer, int cbBufSize, out int pcbBytesNeeded); + } +} diff --git a/Win32/AtomMessenger.cs b/Win32/AtomMessenger.cs new file mode 100644 index 0000000..d2e113f --- /dev/null +++ b/Win32/AtomMessenger.cs @@ -0,0 +1,321 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace Yaulw.Win32 +{ + /// + /// Quick and Easy Atom Window Messages Communication between windows of different + /// Processes. + /// + public class AtomMessenger : IDisposable + { + #region Private Members + private const string _AtomMsgIDFormat = "AtomMsgID-[{0}]"; + private string _AtomMsgStr = ""; + private uint _AtomMsg = 0; + private bool _disposed = false; + + // For Posted Atom Messages + private const int MAGIC_NUMBER_POSTED_ATOM_KEYS = 7; + private List _postedAtomKeys = new List(); + #endregion + + #region Construction + + /// + /// Create an Atom Messaging Component with a Unique Identifier. + /// The same Identifier should be used by all applications that want to pass + /// Messages back and forth. + /// ~You need to dispose of this Object only if you plan on using PostAtomMessage() + /// + /// An identifier for the Messaging + public AtomMessenger(Guid UniqueWindowMessageIdentifier) + { + if (UniqueWindowMessageIdentifier == null) + throw new ArgumentNullException(); + + // Register the Unique Window Message + _AtomMsgStr = String.Format(_AtomMsgIDFormat, UniqueWindowMessageIdentifier); + _AtomMsg = User32.RegisterWindowMessage(_AtomMsgStr); + if (_AtomMsg == 0) + throw new Exception("RegisterWindowMessage Failed"); + } + + /// + /// Finalizer + /// + ~AtomMessenger() + { + Dispose(true); + } + + #endregion + + #region Atom Message Construction Helpers + + /// + /// Retrieve ';' seperated values from an Atom Message + /// + /// an atom message that contains multiple args + /// multiple args + public string[] RetrieveMultipleValuesFromAtomMessage(string AtomMessage) + { + if (!String.IsNullOrEmpty(AtomMessage)) + return AtomMessage.Split(';'); + else + return null; + } + + /// + /// Create ';' seperated Atom message from multiple values + /// + /// an Atom Message containing multiple args + public string SetMultipleValueInAtomMessage(params object[] args) + { + if (args != null) + { + StringBuilder sb = new StringBuilder(); + foreach (object a in args) + { + sb.Append(a.ToString()); + sb.Append(";"); + } + + // Remove Trailing ";" + sb = sb.Remove(sb.Length - 1, 1); + return sb.ToString(); + } + return String.Empty; + } + + #endregion + + #region Atom Message Identify / Retrieval + + /// + /// Use this to check if the passed in Message is an Atom Message. + /// The identifier of the Message must match the Message registered with the UniqueIdentifier + /// + /// a Windows.Forms Message + /// true if the Messages matches the one created by the UniqueIdentifier + public bool IsAtomMessage(ref Message m) + { + return (m.Msg == _AtomMsg); + } + + /// + /// Retrieves the values passed in by an Atom Message + /// + /// a Windows.Forms Message + /// the Message Retrieved from the Atom + /// the source window who send it, if valid + public void GetAtomMessageValues(ref Message m, out string AtomMessage, out IntPtr hWndSrc) + { + AtomMessage = String.Empty; + hWndSrc = IntPtr.Zero; + + // Check to make sure this is an Atom Message + if (!IsAtomMessage(ref m)) + return; + + // Source is passed to us by wParam + if(m.WParam != IntPtr.Zero && User32.IsWindow(m.WParam)) + hWndSrc = m.WParam; + + // Now retrieve the Atom Message + StringBuilder sb = new StringBuilder( 254 ); + uint AtomKey = (uint) m.LParam; + uint slen = Kernel32.GlobalGetAtomName(AtomKey, sb, 254); + if (slen == 0) + return; + + // Write out Retrieved Message + AtomMessage = sb.ToString(); + } + + /// + /// Retrieves the values passed in by an Atom Message + /// + /// a Windows.Forms Message + /// the Message with multiple values retrieved from the Atom + /// the source window who send it, if valid + public void GetAtomMessageValues(ref Message m, out string[] AtomMessage, out IntPtr hWndSrc) + { + AtomMessage = null; + hWndSrc = IntPtr.Zero; + + // Check to make sure this is an Atom Message + if (!IsAtomMessage(ref m)) + return; + + // Source is passed to us by wParam + if (m.WParam != IntPtr.Zero && User32.IsWindow(m.WParam)) + hWndSrc = m.WParam; + + // Now retrieve the Atom Message + StringBuilder sb = new StringBuilder(254); + uint AtomKey = (uint)m.LParam; + uint slen = Kernel32.GlobalGetAtomName(AtomKey, sb, 254); + if (slen == 0) + return; + + // Write out Retrieved Message + AtomMessage = RetrieveMultipleValuesFromAtomMessage(sb.ToString()); + } + + #endregion + + #region Send/Post Atom Message + + /// + /// Sends an Atom Message To the Specified Window or All Windows + /// + /// Sender Windows (Nice to have), in case Receiver Needs it + /// Can be IntPtr.Zero to BroadCast the Message, otherwise specify window to send to + /// The Message to Send * Can not be longer than 254 chars * + public void SendAtomMessage(IntPtr hWndSrc, IntPtr hWndDst, string AtomMessage) + { + // Is Broadcast? + bool bBroadcast = (hWndDst==IntPtr.Zero); + + // Check to make sure Atom Message is proper + if(String.IsNullOrEmpty(AtomMessage) || AtomMessage.Length > 254) + throw new ArgumentException("AtomMessage Invalid"); + + // Register Atom + uint AtomKey = Kernel32.GlobalAddAtom(AtomMessage); + if (AtomKey == 0) + throw new Exception("GlobalAddAtom Failed"); + + // Send the Message + if(bBroadcast) + User32.SendMessage(Definitions.HWND_BROADCAST, (int)_AtomMsg, hWndSrc, (IntPtr)AtomKey); + else + User32.SendMessage(hWndDst, (int)_AtomMsg, hWndSrc, (IntPtr)AtomKey); + + // We are done with the Atom + Kernel32.GlobalDeleteAtom(AtomKey); + } + + /// + /// Sends an Atom Message To the Specified Window or All Windows + /// + /// Sender Windows (Nice to have), in case Receiver Needs it + /// Can be IntPtr.Zero to BroadCast the Message, otherwise specify window to send to + /// The Message to Send that has multiple values * Total string can not be longer than 254 chars * + public void SendAtomMessage(IntPtr hWndSrc, IntPtr hWndDst, string[] AtomMessage) + { + SendAtomMessage(hWndSrc, hWndDst, SetMultipleValueInAtomMessage(AtomMessage)); + } + + /// + /// Post an Atom Message To the Specified Window or All Windows + /// + /// Sender Windows (Nice to have), in case Receiver Needs it + /// Can be IntPtr.Zero to BroadCast the Message, otherwise specify window to send to + /// The Message to Send * Can not be longer than 254 chars * + public void PostAtomMessage(IntPtr hWndSrc, IntPtr hWndDst, string AtomMessage) + { + // Is Broadcast? + bool bBroadcast = (hWndDst == IntPtr.Zero); + + // Check to make sure Atom Message is proper + if (String.IsNullOrEmpty(AtomMessage) || AtomMessage.Length > 254) + throw new ArgumentException("AtomMessage Invalid"); + + // Register a new Atom + uint nAtomKey = Kernel32.GlobalAddAtom(AtomMessage); + if (nAtomKey == 0) + throw new Exception("GlobalAddAtom Failed"); + + // Send the Message + if (bBroadcast) + User32.PostMessage(Definitions.HWND_BROADCAST, (int)_AtomMsg, hWndSrc, (IntPtr)nAtomKey); + else + User32.PostMessage(hWndDst, (int)_AtomMsg, hWndSrc, (IntPtr)nAtomKey); + + // Imp! Atom still must get Deleted, that is why we add it to DS + AddPostedAtomKey(nAtomKey); + } + + /// + /// Post an Atom Message To the Specified Window or All Windows + /// + /// Sender Windows (Nice to have), in case Receiver Needs it + /// Can be IntPtr.Zero to BroadCast the Message, otherwise specify window to send to + /// The Message to Send that has multiple values * Can not be longer than 254 chars * + public void PostAtomMessage(IntPtr hWndSrc, IntPtr hWndDst, string[] AtomMessage) + { + PostAtomMessage(hWndSrc, hWndDst, SetMultipleValueInAtomMessage(AtomMessage)); + } + + #endregion + + #region Posted Atom Keys handling + + /// + /// Adds the Specified Posted Atom Key to the Posted Atoms DS. + /// Clears the DS, if MAGIC_NUMBER_POSTED_ATOM_KEY has been reached. + /// + /// a unique AtomKey + private void AddPostedAtomKey(uint nKey) + { + if (_postedAtomKeys.Count >= MAGIC_NUMBER_POSTED_ATOM_KEYS) + DeleteAllPostedAtomKeys(); + + _postedAtomKeys.Add(nKey); + } + + /// + /// Deletes all posted Atoms and Clears the PostedAtoms DS + /// + private void DeleteAllPostedAtomKeys() + { + foreach (uint Key in _postedAtomKeys) + Kernel32.GlobalDeleteAtom(Key); + + _postedAtomKeys.Clear(); + } + + #endregion + + #region IDisposable Members + + /// + /// Dispose Posted Atom Strings + /// + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer + GC.SuppressFinalize(this); + } + + /// + /// Dispose Posted Atom Strings + /// + /// true, if called from within + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if (_postedAtomKeys.Count != 0) + DeleteAllPostedAtomKeys(); + } + + // Indicate that the instance has been disposed. + _postedAtomKeys = null; + _disposed = true; + } + } + + #endregion + } +} diff --git a/Win32/COM.cs b/Win32/COM.cs new file mode 100644 index 0000000..60f3f17 --- /dev/null +++ b/Win32/COM.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +namespace Yaulw.Win32 +{ + /// + /// COM Win32 Helper Functions + /// + public static class COM + { + /// + /// Returns a pointer to an implementation of IBindCtx (a bind context object). This object stores information about a particular moniker-binding operation + /// + /// This parameter is reserved and must be 0 + /// Address of an IBindCtx* pointer variable that receives the interface pointer to the new bind context object. + /// This function can return the standard return values E_OUTOFMEMORY and S_OK + [DllImport("ole32.dll")] + public static extern int CreateBindCtx(int reserved, out IBindCtx ppbc); + + /// + /// Use this to retrieve the Actice COM Object from the ROT, for the specified progId + /// + /// + /// a valid com object, or null if error occured + public static Object GetActiceCOMObject(string progId) + { + Object app = null; + try + { + app = Marshal.GetActiveObject(progId); + } + catch (SystemException) { /* ignore */ } + return app; + } + + /// + /// ROT Object Entry + /// + public struct RunningObject + { + public string name; + public object o; + } + + /// + /// Use this to Get All Running Objects in the ROT + /// + /// list of all Runnint Objects in the ROT + public static List GetRunningObjects() + { + // Get the table. + var res = new List(); + IBindCtx bc; + + CreateBindCtx(0, out bc); + IRunningObjectTable runningObjectTable; + + bc.GetRunningObjectTable(out runningObjectTable); + IEnumMoniker monikerEnumerator; + runningObjectTable.EnumRunning(out monikerEnumerator); + monikerEnumerator.Reset(); + + // Enumerate and fill our nice dictionary. + IMoniker[] monikers = new IMoniker[1]; + IntPtr numFetched = IntPtr.Zero; + + while (monikerEnumerator.Next(1, monikers, numFetched) == 0) + { + RunningObject running; + monikers[0].GetDisplayName(bc, null, out running.name); + runningObjectTable.GetObject(monikers[0], out running.o); + res.Add(running); + } + return res; + } + + /// + /// Use this to Get A specific type of Object from the ROT + /// + /// List of a specific type of Object in the ROT + public static List GetRunningObjectsOfType() + { + // Get the table. + var res = new List(); + IBindCtx bc; + + CreateBindCtx(0, out bc); + IRunningObjectTable runningObjectTable; + + bc.GetRunningObjectTable(out runningObjectTable); + IEnumMoniker monikerEnumerator; + runningObjectTable.EnumRunning(out monikerEnumerator); + monikerEnumerator.Reset(); + + // Enumerate and fill our nice dictionary. + IMoniker[] monikers = new IMoniker[1]; + IntPtr numFetched = IntPtr.Zero; + + while (monikerEnumerator.Next(1, monikers, numFetched) == 0) + { + object o; + runningObjectTable.GetObject(monikers[0], out o); + + if (o is T) + res.Add((T)o); + o = null; + } + return res; + } + } + +} diff --git a/Win32/Convert.cs b/Win32/Convert.cs new file mode 100644 index 0000000..90af9df --- /dev/null +++ b/Win32/Convert.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using System.Drawing; + +namespace Yaulw.Win32 +{ + /// + /// Helper class to Convert Win32 Structures to DotNet and vice versa + /// + public static class Convert + { + /// + /// Use this to Convert to Win32 Window Message Enum from a C# Message + /// + /// a C# Message + /// a Win32 Message Enum + public static Definitions.WM MessageToWin32WM(Message m) + { + Definitions.WM wm = Definitions.WM.WM_NULL; + try { wm = (Definitions.WM)m.Msg; } + catch (Exception) { wm = Definitions.WM.WM_NULL; } + return wm; + } + + /// + /// Use this to Convert to a C# Rectangle from a Win32 RECT + /// + /// a Win32 Rect + /// a C# Rectancle + public static Rectangle RECTToRectangle(Structures.RECT rect) + { + return new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + } + + /// + /// Use this to Convert a Win32 RECT to a C# Rectangle + /// + /// a C# Rectangle + /// a Win32 Rect + public static Structures.RECT RectangleToRECT(Rectangle rect) + { + return Structures.RECT.FromXYWH(rect.Left, rect.Top, rect.Width, rect.Height); + } + + #region Win32Owner + + /// + /// a IWin32Window Object commonly used by WinForms + /// + public class Win32Owner : IWin32Window + { + public IntPtr Handle { get; set; } + } + + /// + /// Convert a Handle to a Win32Owner Object + /// + /// pass in a Window Handle + /// a newly created Win32Owner Object + public static IWin32Window ConverthWndToIWin32Window(IntPtr hWnd) + { + return new Win32Owner() { Handle = hWnd }; + } + + #endregion + } +} diff --git a/Win32/Definitions.cs b/Win32/Definitions.cs new file mode 100644 index 0000000..96bef3b --- /dev/null +++ b/Win32/Definitions.cs @@ -0,0 +1,601 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Yaulw.Win32 +{ + /// + /// Win32 Enums and Consts (Definitions) + /// + public static class Definitions + { + #region Constants + + /// + /// Virtual Key Board Key Up Event + /// + public const ushort KEYEVENTF_KEYUP = 0x0002; + + /// + /// Source Copy Option + /// + public const int SRCCOPY = 13369376; + + /// + /// Default Monitor Nearest + /// + public const int MONITOR_DEFAULTTONEAREST = 0x00000002; + + /// + /// Universal Max Path Const used in Many Win32 Paths + /// + public const int MAX_PATH = 255; + + /// + /// For Windows Hooking + /// + public const int WH_CBT = 5; + + /// + /// System is about to activate a window + /// + public const int HCBT_ACTIVATE = 5; + + /// + /// Used for SendMessage/PostMessage + /// + public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff); + + #region Service Constants - Advapi32.dll + + /// + /// Service Option + /// + public const int SERVICE_WIN32_OWN_PROCESS = 0x00000010; + + /// + /// Service Option + /// + public const int SERVICE_RUNS_IN_SYSTEM_PROCESS = 0x00000001; + + /// + /// Service Option + /// + public const int SC_STATUS_PROCESS_INFO = 0; + + #endregion + + #endregion + + #region Enums + + /// + /// AssocQueryString Options + /// + public enum AssocF : ushort + { + Init_NoRemapCLSID = 0x1, + Init_ByExeName = 0x2, + Open_ByExeName = 0x2, + Init_DefaultToStar = 0x4, + Init_DefaultToFolder = 0x8, + NoUserSettings = 0x10, + NoTruncate = 0x20, + Verify = 0x40, + RemapRunDll = 0x80, + NoFixUps = 0x100, + IgnoreBaseClass = 0x200 + } + + /// + /// DDE Commands + /// + public enum AssocStr : ushort + { + Command = 1, + Executable, + FriendlyDocName, + FriendlyAppName, + NoOpen, + ShellNewValue, + DDECommand, + DDEIfExec, + DDEApplication, + DDETopic + } + + /// + /// For Process DEP + /// + public enum DEP : int + { + PROCESS_DEP_DISABLE = 0x00000000, + PROCESS_DEP_ENABLE = 0x00000001, + PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION = 0x00000002, + } + + /// + /// Layered Windows + /// + public enum LayeredWindowAttribute_Value : int + { + LWA_COLORKEY = 0x00000001, + LWA_ALPHA = 0x00000002 + } + + /// + /// Mouse Events + /// + public enum MouseEvent : ushort + { + MOUSEEVENTF_LEFTDOWN = 0x02, + MOUSEEVENTF_LEFTUP = 0x04, + MOUSEEVENTF_RIGHTDOWN = 0x08, + MOUSEEVENTF_RIGHTUP = 0x10 + } + + /// + /// OpenProcess Values + /// + public enum OpenProcess_Value : int + { + DELETE = 0x00010000, + READ_CONTROL = 0x00020000, + SYNCHRONIZE = 0x00100000, + WRITE_DAC = 0x00040000, + WRITE_OWNER = 0x00080000 + } + + /// + /// Scrollbar values + /// + public enum ScrollBar_Value : int + { + SB_HORZ = 0, + SB_VERT = 1, + SB_CTL = 2, + SB_BOTH = 3, + } + + /// + /// Set Window Pos Actions + /// + public enum SetWindowPos_Action : uint + { + SWP_NOSIZE = 0x0001, + SWP_NOMOVE = 0x0002, + SWP_NOZORDER = 0x0004, + SWP_NOREDRAW = 0x0008, + SWP_NOACTIVATE = 0x0010, + SWP_FRAMECHANGED = 0x0020, + SWP_SHOWWINDOW = 0x0040, + SWP_HIDEWINDOW = 0x0080, + SWP_NOCOPYBITS = 0x0100, + SWP_NOOWNERZORDER = 0x0200, + SWP_NOSENDCHANGING = 0x0400, + SWP_DRAWFRAME = SWP_FRAMECHANGED, + SWP_NOREPOSITION = SWP_NOOWNERZORDER + } + + /// + /// Show Window Actions + /// + public enum ShowWindow_Action : int + { + SW_HIDE = 0, + SW_SHOWNORMAL = 1, + SW_NORMAL = 1, + SW_SHOWMINIMIZED = 2, + SW_SHOWMAXIMIZED = 3, + SW_MAXIMIZE = 3, + SW_SHOWNOACTIVATE = 4, + SW_SHOW = 5, + SW_MINIMIZE = 6, + SW_SHOWMINNOACTIVE = 7, + SW_SHOWNA = 8, + SW_RESTORE = 9, + SW_SHOWDEFAULT = 10, + SW_FORCEMINIMIZE = 11, + SW_MAX = 11 + } + + /// + /// System Metrix + /// + public enum SystemMetric_Value : int + { + SM_CXSCREEN = 0, + SM_CYSCREEN = 1, + SM_CXVSCROLL = 2, + SM_CYHSCROLL = 3, + SM_CYCAPTION = 4, + SM_CXBORDER = 5, + SM_CYBORDER = 6, + SM_CXDLGFRAME = 7, + SM_CYDLGFRAME = 8, + SM_CYVTHUMB = 9, + SM_CXHTHUMB = 10, + SM_CXICON = 11, + SM_CYICON = 12, + SM_CXCURSOR = 13, + SM_CYCURSOR = 14, + SM_CYMENU = 15, + SM_CXFULLSCREEN = 16, + SM_CYFULLSCREEN = 17, + SM_CYKANJIWINDOW = 18, + SM_MOUSEPRESENT = 19, + SM_CYVSCROLL = 20, + SM_CXHSCROLL = 21, + SM_DEBUG = 22, + SM_SWAPBUTTON = 23, + SM_RESERVED1 = 24, + SM_RESERVED2 = 25, + SM_RESERVED3 = 26, + SM_RESERVED4 = 27, + SM_CXMIN = 28, + SM_CYMIN = 29, + SM_CXSIZE = 30, + SM_CYSIZE = 31, + SM_CXFRAME = 32, + SM_CYFRAME = 33, + SM_CXMINTRACK = 34, + SM_CYMINTRACK = 35, + SM_CXDOUBLECLK = 36, + SM_CYDOUBLECLK = 37, + SM_CXICONSPACING = 38, + SM_CYICONSPACING = 39, + SM_MENUDROPALIGNMENT = 40, + SM_PENWINDOWS = 41, + SM_DBCSENABLED = 42, + SM_CMOUSEBUTTONS = 43 + } + + /// + /// GetWindowLong Values + /// + public enum WindowLong_Value : int + { + GWL_EXSTYLE = -20, + GWL_STYLE = -16, + GWL_WNDPROC = -4, + GWL_HINSTANCE = -6, + GWL_ID = -12, + GWL_USERDATA = -21, + DWL_DLGPROC = 4, + DWL_MSGRESULT = 0, + DWL_USER = 8 + } + + /// + /// Window Positions + /// + public enum WindowPos_Value : int + { + HWND_TOP = 0, + HWND_BOTTOM = 1, + HWND_TOPMOST = -1, + HWND_NOTOPMOST = -2 + } + + /// + /// Windows Styles + /// + public enum WindowStyle_Value : uint + { + WS_MAXIMIZE = 0x01000000, + WS_VISIBLE = 0x10000000, + WS_POPUP = 0x80000000, + WS_BORDER = 0x00800000, + WS_CAPTION = 0x00C00000, + WS_CHILD = 0x40000000, + WS_CHILDWINDOW = 0x40000000, + WS_EX_CLIENTEDGE = 0x00000200, + WS_CLIPCHILDREN = 0x02000000, + WS_CLIPSIBLINGS = 0x04000000, + WS_DISABLED = 0x08000000, + WS_DLGFRAME = 0x00400000, + WS_GROUP = 0x00020000, + } + + /// + /// Windows Messages + /// + public enum WM : int + { + WM_NULL = 0x0000, + WM_CREATE = 0x0001, + WM_DESTROY = 0x0002, + WM_MOVE = 0x0003, + WM_SIZE = 0x0005, + WM_ACTIVATE = 0x0006, + WM_SETFOCUS = 0x0007, + WM_KILLFOCUS = 0x0008, + WM_ENABLE = 0x000A, + WM_SETREDRAW = 0x000B, + WM_SETTEXT = 0x000C, + WM_GETTEXT = 0x000D, + WM_GETTEXTLENGTH = 0x000E, + WM_PAINT = 0x000F, + WM_CLOSE = 0x0010, + WM_QUERYENDSESSION = 0x0011, + WM_QUIT = 0x0012, + WM_QUERYOPEN = 0x0013, + WM_ERASEBKGND = 0x0014, + WM_SYSCOLORCHANGE = 0x0015, + WM_ENDSESSION = 0x0016, + WM_SHOWWINDOW = 0x0018, + WM_CTLCOLOR = 0x0019, + WM_WININICHANGE = 0x001A, + WM_SETTINGCHANGE = 0x001A, + WM_DEVMODECHANGE = 0x001B, + WM_ACTIVATEAPP = 0x001C, + WM_FONTCHANGE = 0x001D, + WM_TIMECHANGE = 0x001E, + WM_CANCELMODE = 0x001F, + WM_SETCURSOR = 0x0020, + WM_MOUSEACTIVATE = 0x0021, + WM_CHILDACTIVATE = 0x0022, + WM_QUEUESYNC = 0x0023, + WM_GETMINMAXINFO = 0x0024, + WM_PAINTICON = 0x0026, + WM_ICONERASEBKGND = 0x0027, + WM_NEXTDLGCTL = 0x0028, + WM_SPOOLERSTATUS = 0x002A, + WM_DRAWITEM = 0x002B, + WM_MEASUREITEM = 0x002C, + WM_DELETEITEM = 0x002D, + WM_VKEYTOITEM = 0x002E, + WM_CHARTOITEM = 0x002F, + WM_SETFONT = 0x0030, + WM_GETFONT = 0x0031, + WM_SETHOTKEY = 0x0032, + WM_GETHOTKEY = 0x0033, + WM_QUERYDRAGICON = 0x0037, + WM_COMPAREITEM = 0x0039, + WM_GETOBJECT = 0x003D, + WM_COMPACTING = 0x0041, + WM_COMMNOTIFY = 0x0044, + WM_WINDOWPOSCHANGING = 0x0046, + WM_WINDOWPOSCHANGED = 0x0047, + WM_POWER = 0x0048, + WM_COPYDATA = 0x004A, + WM_CANCELJOURNAL = 0x004B, + WM_NOTIFY = 0x004E, + WM_INPUTLANGCHANGEREQUEST = 0x0050, + WM_INPUTLANGCHANGE = 0x0051, + WM_TCARD = 0x0052, + WM_HELP = 0x0053, + WM_USERCHANGED = 0x0054, + WM_NOTIFYFORMAT = 0x0055, + WM_CONTEXTMENU = 0x007B, + WM_STYLECHANGING = 0x007C, + WM_STYLECHANGED = 0x007D, + WM_DISPLAYCHANGE = 0x007E, + WM_GETICON = 0x007F, + WM_SETICON = 0x0080, + WM_NCCREATE = 0x0081, + WM_NCDESTROY = 0x0082, + WM_NCCALCSIZE = 0x0083, + WM_NCHITTEST = 0x0084, + WM_NCPAINT = 0x0085, + WM_NCACTIVATE = 0x0086, + WM_GETDLGCODE = 0x0087, + WM_SYNCPAINT = 0x0088, + WM_NCMOUSEMOVE = 0x00A0, + WM_NCLBUTTONDOWN = 0x00A1, + WM_NCLBUTTONUP = 0x00A2, + WM_NCLBUTTONDBLCLK = 0x00A3, + WM_NCRBUTTONDOWN = 0x00A4, + WM_NCRBUTTONUP = 0x00A5, + WM_NCRBUTTONDBLCLK = 0x00A6, + WM_NCMBUTTONDOWN = 0x00A7, + WM_NCMBUTTONUP = 0x00A8, + WM_NCMBUTTONDBLCLK = 0x00A9, + WM_KEYDOWN = 0x0100, + WM_KEYUP = 0x0101, + WM_CHAR = 0x0102, + WM_DEADCHAR = 0x0103, + WM_SYSKEYDOWN = 0x0104, + WM_SYSKEYUP = 0x0105, + WM_SYSCHAR = 0x0106, + WM_SYSDEADCHAR = 0x0107, + WM_KEYLAST = 0x0108, + WM_IME_STARTCOMPOSITION = 0x010D, + WM_IME_ENDCOMPOSITION = 0x010E, + WM_IME_COMPOSITION = 0x010F, + WM_IME_KEYLAST = 0x010F, + WM_INITDIALOG = 0x0110, + WM_COMMAND = 0x0111, + WM_SYSCOMMAND = 0x0112, + WM_TIMER = 0x0113, + WM_HSCROLL = 0x0114, + WM_VSCROLL = 0x0115, + WM_INITMENU = 0x0116, + WM_INITMENUPOPUP = 0x0117, + WM_MENUSELECT = 0x011F, + WM_MENUCHAR = 0x0120, + WM_ENTERIDLE = 0x0121, + WM_MENURBUTTONUP = 0x0122, + WM_MENUDRAG = 0x0123, + WM_MENUGETOBJECT = 0x0124, + WM_UNINITMENUPOPUP = 0x0125, + WM_MENUCOMMAND = 0x0126, + WM_CTLCOLORMSGBOX = 0x0132, + WM_CTLCOLOREDIT = 0x0133, + WM_CTLCOLORLISTBOX = 0x0134, + WM_CTLCOLORBTN = 0x0135, + WM_CTLCOLORDLG = 0x0136, + WM_CTLCOLORSCROLLBAR = 0x0137, + WM_CTLCOLORSTATIC = 0x0138, + WM_MOUSEMOVE = 0x0200, + WM_LBUTTONDOWN = 0x0201, + WM_LBUTTONUP = 0x0202, + WM_LBUTTONDBLCLK = 0x0203, + WM_RBUTTONDOWN = 0x0204, + WM_RBUTTONUP = 0x0205, + WM_RBUTTONDBLCLK = 0x0206, + WM_MBUTTONDOWN = 0x0207, + WM_MBUTTONUP = 0x0208, + WM_MBUTTONDBLCLK = 0x0209, + WM_MOUSEWHEEL = 0x020A, + WM_XBUTTONDOWN = 0x020B, + WM_XBUTTONUP = 0x020C, + WM_XBUTTONDBLCLK = 0x020D, + WM_PARENTNOTIFY = 0x0210, + WM_ENTERMENULOOP = 0x0211, + WM_EXITMENULOOP = 0x0212, + WM_NEXTMENU = 0x0213, + WM_SIZING = 0x0214, + WM_CAPTURECHANGED = 0x0215, + WM_MOVING = 0x0216, + WM_DEVICECHANGE = 0x0219, + WM_MDICREATE = 0x0220, + WM_MDIDESTROY = 0x0221, + WM_MDIACTIVATE = 0x0222, + WM_MDIRESTORE = 0x0223, + WM_MDINEXT = 0x0224, + WM_MDIMAXIMIZE = 0x0225, + WM_MDITILE = 0x0226, + WM_MDICASCADE = 0x0227, + WM_MDIICONARRANGE = 0x0228, + WM_MDIGETACTIVE = 0x0229, + WM_MDISETMENU = 0x0230, + WM_ENTERSIZEMOVE = 0x0231, + WM_EXITSIZEMOVE = 0x0232, + WM_DROPFILES = 0x0233, + WM_MDIREFRESHMENU = 0x0234, + WM_IME_SETCONTEXT = 0x0281, + WM_IME_NOTIFY = 0x0282, + WM_IME_CONTROL = 0x0283, + WM_IME_COMPOSITIONFULL = 0x0284, + WM_IME_SELECT = 0x0285, + WM_IME_CHAR = 0x0286, + WM_IME_REQUEST = 0x0288, + WM_IME_KEYDOWN = 0x0290, + WM_IME_KEYUP = 0x0291, + WM_MOUSEHOVER = 0x02A1, + WM_MOUSELEAVE = 0x02A3, + WM_CUT = 0x0300, + WM_COPY = 0x0301, + WM_PASTE = 0x0302, + WM_CLEAR = 0x0303, + WM_UNDO = 0x0304, + WM_RENDERFORMAT = 0x0305, + WM_RENDERALLFORMATS = 0x0306, + WM_DESTROYCLIPBOARD = 0x0307, + WM_DRAWCLIPBOARD = 0x0308, + WM_PAINTCLIPBOARD = 0x0309, + WM_VSCROLLCLIPBOARD = 0x030A, + WM_SIZECLIPBOARD = 0x030B, + WM_ASKCBFORMATNAME = 0x030C, + WM_CHANGECBCHAIN = 0x030D, + WM_HSCROLLCLIPBOARD = 0x030E, + WM_QUERYNEWPALETTE = 0x030F, + WM_PALETTEISCHANGING = 0x0310, + WM_PALETTECHANGED = 0x0311, + WM_HOTKEY = 0x0312, + WM_PRINT = 0x0317, + WM_PRINTCLIENT = 0x0318, + WM_HANDHELDFIRST = 0x0358, + WM_HANDHELDLAST = 0x035F, + WM_AFXFIRST = 0x0360, + WM_AFXLAST = 0x037F, + WM_PENWINFIRST = 0x0380, + WM_PENWINLAST = 0x038F, + WM_APP = 0x8000, + WM_USER = 0x0400, + WM_REFLECT = WM_USER + 0x1c00, + WM_CHANGEUISTATE = 0x0127, + WM_UPDATEUISTATE = 0x0128, + WM_QUERYUISTATE = 0x0129 + } + + /// + /// Virtual Keyboard Keys + /// + public enum VK : ushort + { + SHIFT = 0x10, + CONTROL = 0x11, + MENU = 0x12, + ESCAPE = 0x1B, + BACK = 0x08, + TAB = 0x09, + RETURN = 0x0D, + SPACE = 0x20, + PRIOR = 0x21, + NEXT = 0x22, + END = 0x23, + HOME = 0x24, + LEFT = 0x25, + UP = 0x26, + RIGHT = 0x27, + DOWN = 0x28, + SELECT = 0x29, + PRINT = 0x2A, + EXECUTE = 0x2B, + SNAPSHOT = 0x2C, + INSERT = 0x2D, + DELETE = 0x2E, + HELP = 0x2F, + NUMPAD0 = 0x60, + NUMPAD1 = 0x61, + NUMPAD2 = 0x62, + NUMPAD3 = 0x63, + NUMPAD4 = 0x64, + NUMPAD5 = 0x65, + NUMPAD6 = 0x66, + NUMPAD7 = 0x67, + NUMPAD8 = 0x68, + NUMPAD9 = 0x69, + MULTIPLY = 0x6A, + ADD = 0x6B, + SEPARATOR = 0x6C, + SUBTRACT = 0x6D, + DECIMAL = 0x6E, + DIVIDE = 0x6F, + F1 = 0x70, + F2 = 0x71, + F3 = 0x72, + F4 = 0x73, + F5 = 0x74, + F6 = 0x75, + F7 = 0x76, + F8 = 0x77, + F9 = 0x78, + F10 = 0x79, + F11 = 0x7A, + F12 = 0x7B, + OEM_1 = 0xBA, // ',:' for US + OEM_PLUS = 0xBB, // '+' any country + OEM_COMMA = 0xBC, // ',' any country + OEM_MINUS = 0xBD, // '-' any country + OEM_PERIOD = 0xBE, // '.' any country + OEM_2 = 0xBF, // '/?' for US + OEM_3 = 0xC0, // '`~' for US + MEDIA_NEXT_TRACK = 0xB0, + MEDIA_PREV_TRACK = 0xB1, + MEDIA_STOP = 0xB2, + MEDIA_PLAY_PAUSE = 0xB3, + LWIN = 0x5B, + RWIN = 0x5C + } + + /// + /// enum to hold the possible connection states + /// + [Flags] + public enum ConnectionStatusEnum : int + { + INTERNET_CONNECTION_MODEM = 0x1, + INTERNET_CONNECTION_LAN = 0x2, + INTERNET_CONNECTION_PROXY = 0x4, + INTERNET_RAS_INSTALLED = 0x10, + INTERNET_CONNECTION_OFFLINE = 0x20, + INTERNET_CONNECTION_CONFIGURED = 0x40 + } + + #endregion + } +} diff --git a/Win32/Functions.cs b/Win32/Functions.cs new file mode 100644 index 0000000..78112b3 --- /dev/null +++ b/Win32/Functions.cs @@ -0,0 +1,388 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; +using System.Diagnostics; +using System.Windows.Forms; +using System.Runtime.InteropServices; +using SysIO = System.IO; +using Diag = System.Diagnostics; +using Yaulw.Tools; + +namespace Yaulw.Win32 +{ + /// + /// Common Win32 .Net Wrapper Functions around Win32 for easier consumption by C# + /// + public static class Functions + { + #region Byte / Word + + /// + /// Returns the Low word of the specified int + /// + /// int to retrieve low word from + /// low word of the int + public static int LOWORD(int n) { return (n & 0xffff); } + + /// + /// Returns the Low byte of the specified int + /// + /// int to retrieve low byte from + /// low byte of the int + public static int LOBYTE(int n) { return (n & 0xff); } + + /// + /// Returns the High word of the specified int + /// + /// int to retrieve high word from + /// high word of the int + public static int HIWORD(int n) { return ((n >> 16) & 0xffff); } + + /// + /// Returns the High byte of the specified int + /// + /// int to retrieve high byte from + /// high byte of the int + public static int HIBYTE(int n) { return ((n >> 8) & 0xff); } + + #endregion + + #region RGB + + /// + /// Win32 RGB + /// + /// red value as int + /// green value as int + /// blue value as int + /// returns a Win32 RGB Value as int + public static int RGB(int r, int g, int b) + { + int rs = r & 0xffff; + int gs = (g << 8) & 0xffff; + int bs = (b << 16) & 0xffff; + return (rs | gs | bs); + } + + /// + /// Retrieve the Red Value from Win32 RGB Value + /// + /// + /// returns Red value + public static int GetRValue(int rgbDWORD) + { + return LOBYTE(rgbDWORD); + } + + /// + /// Retrieve the Green Value from Win32 RGB Value + /// + /// + /// returns Green value + public static int GetGValue(int rgbDWORD) + { + return (LOBYTE(rgbDWORD >> 8)); + } + + /// + /// Retrieve the Blue Value from Win32 RGB Value + /// + /// + /// returns Blue value + public static int GetBValue(int rgbDWORD) + { + return (LOBYTE(rgbDWORD >> 16)); + } + + #endregion + + #region Keyboard Functionallity + + /// + /// Use this Function to determine whether the user is holding down a specified key + /// + /// VK such as Ctrl/Alt/Shift + /// true if pressed down, false otherwise + public static bool IsKeyPressed(Definitions.VK vKey) + { + short retVal = User32.GetKeyState(vKey); + return (retVal < 0); + } + + #endregion + + #region ClientRect Functionallity + + /// + /// Get a windows client rectangle + /// + /// A Window Handle + /// A Rectangle + public static Rectangle GetClientRect(IntPtr hWnd) + { + Structures.RECT rect = new Structures.RECT(); + User32.GetClientRect(hWnd, out rect); + return Convert.RECTToRectangle(rect); + } + + /// + /// Get a windows rectangle + /// + /// The Window handle + /// A Rectangle + public static Rectangle GetWindowRect(IntPtr hWnd) + { + Structures.RECT rect = new Structures.RECT(); + User32.GetWindowRect(hWnd, out rect); + return Convert.RECTToRectangle(rect); + } + + /// + /// Returns the Client Size, taking into account the Chrome width + /// + /// A Window Handle + /// A Rectangle + public static Rectangle GetAbsoluteClientRect(IntPtr hWnd) + { + Rectangle windowRect = GetWindowRect(hWnd); + Rectangle clientRect = GetClientRect(hWnd); + int chromeWidth = (int)((windowRect.Width - clientRect.Width) / 2); + return new Rectangle(new Point(windowRect.X + chromeWidth, windowRect.Y + (windowRect.Height - clientRect.Height - chromeWidth)), clientRect.Size); + } + + #endregion + + #region Window Functionallity + + /// + /// Use this to get the name of a Window Class + /// + /// A Window Handle + /// The name of the Window Class + public static string GetWindowClassName(IntPtr hWnd) + { + StringBuilder ClassName = new StringBuilder(100); + int nRet = User32.GetClassName(hWnd, ClassName, ClassName.Capacity); + return ClassName.ToString(); + } + + /// + /// Use this to get the text of a Window + /// + /// A Window Handle + /// The window Text + public static string GetWindowText(IntPtr hWnd) + { + int length = User32.GetWindowTextLength(hWnd); + StringBuilder sb = new StringBuilder(length + 1); + User32.GetWindowText(hWnd, sb, sb.Capacity); + return sb.ToString(); + } + + /// + /// Get the Window Text of a Dialog Item + /// + /// handle to Dialog + /// uint Dialog Item + /// Window Text from the Dialog Item + public static string GetDlgItemText(IntPtr hDlg, int nIDDlgItem) + { + // Get the handle of the dialog item + IntPtr hItem = User32.GetDlgItem(hDlg, nIDDlgItem); + if (hItem == IntPtr.Zero) + return String.Empty; + + return GetWindowText(hItem); + } + + /// + /// Use this to retrieve the Process Object for a specific Window Handle + /// + /// A Window Handle + /// A valid Process object or Null if error occured + public static System.Diagnostics.Process GetProcessFromWindowHandle(IntPtr hWnd) + { + int findPID = 0; + User32.GetWindowThreadProcessId(hWnd, ref findPID); + System.Diagnostics.Process process = null; + try + { + if (findPID > 0) + process = System.Diagnostics.Process.GetProcessById(findPID); + } + catch (ArgumentException) { /* ignore */ } + return process; + } + + /// + /// Find the first top level window for the specified process + /// + /// Process ID + /// the hWnd for the first Window of the specifed or IntPtr.Zero if none found + public static IntPtr GetFirstTopLevelWindowForProcess(int pid) + { + IntPtr Result = IntPtr.Zero; + + // Enumerate the top-level windows + User32.EnumWindowsProc proc = delegate(IntPtr hWnd, IntPtr lParam) + { + // Find the first Window that belongs to the specified Process + if (User32.IsWindowVisible(hWnd) && (pid == GetProcessFromWindowHandle(hWnd).Id)) + { + Result = hWnd; + return false; + } + return true; // Keep Looking + }; + User32.EnumWindows(proc, IntPtr.Zero); + + // yippie + return Result; + } + + /// + /// + /// + /// + /// + public static IntPtr GetTopLevelChildWindowForProcess(int pid) + { + // To Do + return IntPtr.Zero; + } + + #endregion + + #region Desktop Window Functionallity + + /// + /// Get's the Desktop Window as a Win32Window Object + /// + /// a Win32Window Object + public static IWin32Window GetDestopWindow() + { + return Convert.ConverthWndToIWin32Window(User32.GetDesktopWindow()); + } + + /// + /// Refreshes orphaned Icons in the Taskbar + /// + /// + /// + public static void RefreshTaskbarNotificationArea() + { + // Find the Notification Area + IntPtr hNotificationArea = IntPtr.Zero; + hNotificationArea = User32.FindWindowEx + (User32.FW(User32.FW(User32.FW(IntPtr.Zero, "Shell_TrayWnd"), "TrayNotifyWnd"), "SysPager"), + IntPtr.Zero, + "ToolbarWindow32", + "Notification Area"); + + if (hNotificationArea == IntPtr.Zero || !User32.IsWindow(hNotificationArea)) + return; + + // Get the Client Rect of the Notification Area + Structures.RECT r; + User32.GetClientRect(hNotificationArea, out r); + + // Send Mouse Messages to the Notification Area + for (int x = 0; x < r.right; x += 5) + for (int y = 0; y < r.bottom; y += 5) + User32.SendMessage(hNotificationArea, (int) Definitions.WM.WM_MOUSEMOVE, (IntPtr) 0, (IntPtr) ((y << 16) + x)); + } + + #endregion + + #region Process Kernel Functionallity + + // SetProcessDEPPolicy Helpers + private delegate bool SetProcessDEPPolicy_Delegate(int dwFlags); + private static SetProcessDEPPolicy_Delegate SetProcessPolicy = null; + + /// + /// Use SetProcessDEPPolicy to set the DEP Policy for the currently running + /// Process. This function checks to make sure that kernel32 has the function, + /// before calling it. For Windows Systems that don't have DEP. + /// + /// the specified dep to set + /// the return value of the SetProcessDEPPolicy, or false if it doesn't exist + public static bool SetProcessDEPPolicy(Definitions.DEP dep) + { + IntPtr hKernel32 = Kernel32.LoadLibrary("Kernel32.dll"); // Get the DLL + if (hKernel32 != IntPtr.Zero) + { + IntPtr procaddr = IntPtr.Zero; + procaddr = Kernel32.GetProcAddress(hKernel32, "SetProcessDEPPolicy"); // Get the Function + if (procaddr != null) + { + // Cast the Delegate + SetProcessPolicy = (SetProcessDEPPolicy_Delegate)Marshal.GetDelegateForFunctionPointer(procaddr, typeof(SetProcessDEPPolicy_Delegate)); + + // Call it * Disabling DEP * + return SetProcessPolicy((int)dep); + } + } + return false; + } + + #endregion + + #region Short / Long FileName N' Path Conversions + + /// + /// Converts a LongFileNameNPath (Modern Windows Path) into a ShortFileNPath (Old Dos 8.3) + /// ~File Must exist on the system + /// + /// Long (Modern Windows Path) FileNameNPath or Path + /// Old Dos 8.3 Path + public static string GetShortFileNameNPathOrPath(string longFileNameNPathOrPath) + { + if (String.IsNullOrEmpty(longFileNameNPathOrPath)) + return String.Empty; + + // File MUST exist on the system * Otherwise conversions will fail * + if(PathNaming.PathContainsFile(longFileNameNPathOrPath) && !SysIO.File.Exists(longFileNameNPathOrPath)) + return String.Empty; + + // Directory MUST exist on the system * Otherwise conversions will fail * + if(!PathNaming.PathContainsFile(longFileNameNPathOrPath) && !SysIO.Directory.Exists(longFileNameNPathOrPath)) + return String.Empty; + + if (String.IsNullOrEmpty(longFileNameNPathOrPath) || !SysIO.Directory.Exists(longFileNameNPathOrPath)) + return ""; + + StringBuilder sb = new StringBuilder(Definitions.MAX_PATH); + Kernel32.GetShortPathName(longFileNameNPathOrPath, sb, sb.Capacity); + return sb.ToString(); + } + + /// + /// Converts a ShortFileNameNPath (Old Dos 8.3) into a long (Modern Windows Path) + /// ~File Must exist on the system + /// + /// Old Dos 8.3 FileNameNPath or Path + /// new Modern Windows Path + public static string GetLongFileNameNPathOrPath(string shortFileNameNPathOrPath) + { + if (String.IsNullOrEmpty(shortFileNameNPathOrPath)) + return String.Empty; + + // File MUST exist on the system * Otherwise conversions will fail * + if (PathNaming.PathContainsFile(shortFileNameNPathOrPath) && !SysIO.File.Exists(shortFileNameNPathOrPath)) + return String.Empty; + + // Directory MUST exist on the system * Otherwise conversions will fail * + if (!PathNaming.PathContainsFile(shortFileNameNPathOrPath) && !SysIO.Directory.Exists(shortFileNameNPathOrPath)) + return String.Empty; + + StringBuilder sb = new StringBuilder(Definitions.MAX_PATH); + Kernel32.GetLongPathName(shortFileNameNPathOrPath, sb, sb.Capacity); + return sb.ToString(); + } + + #endregion + } +} diff --git a/Win32/Gdi32.cs b/Win32/Gdi32.cs new file mode 100644 index 0000000..f147b6c --- /dev/null +++ b/Win32/Gdi32.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace Yaulw.Win32 +{ + /// + /// Gdi32.dll Entry Points * http://pinvoke.net/ * + /// + public static class Gdi32 + { + /// + /// The BitBlt function performs a bit-block transfer of the color data corresponding to a rectangle of pixels from + /// the specified source device context into a destination device context. + /// + /// A handle to the destination device context + /// The x-coordinate, in logical units, of the upper-left corner of the destination rectangle + /// The y-coordinate, in logical units, of the upper-left corner of the destination rectangle + /// he width, in logical units, of the source and destination rectangles + /// The height, in logical units, of the source and the destination rectangles + /// A handle to the source device context + /// The x-coordinate, in logical units, of the upper-left corner of the source rectangle + /// The y-coordinate, in logical units, of the upper-left corner of the source rectangle + /// A raster-operation code. These codes define how the color data for the source rectangle is to be combined with the color data for the destination rectangle to achieve the final color + /// If the function succeeds, the return value is nonzero + [DllImport("gdi32.dll")] + extern public static bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, int RasterOp); + + /// + /// This function creates a bitmap compatible with the device associated with the specified device context + /// + /// Handle to a device context + /// Specifies the bitmap width, in pixels + /// Specifies the bitmap height, in pixels + /// A handle to the bitmap indicates success. NULL indicates failure. + [DllImport("gdi32.dll")] + extern public static IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight); + + /// + /// This function creates a memory device context (DC) compatible with the specified device + /// + /// Handle to an existing device context + /// The handle to a memory device context indicates success. NULL indicates failure. + [DllImport("gdi32.dll")] + extern public static IntPtr CreateCompatibleDC(IntPtr hdc); + + /// + /// + /// + /// + /// + /// + /// + /// + [DllImport("gdi32.dll")] + extern public static IntPtr CreateDC(string lpszDriver, IntPtr passNULL, IntPtr passNULL2, IntPtr passNULL3); + + /// + /// + /// + /// + /// + [DllImport("gdi32.dll")] + extern public static IntPtr DeleteDC(IntPtr hDc); + + /// + /// + /// + /// + /// + [DllImport("gdi32.dll")] + extern public static IntPtr DeleteObject(IntPtr hDc); + + /// + /// + /// + /// + /// + /// + [DllImport("gdi32.dll")] + extern public static IntPtr SelectObject(IntPtr hdc, IntPtr bmp); + } +} diff --git a/Win32/Kernel32.cs b/Win32/Kernel32.cs new file mode 100644 index 0000000..25daef6 --- /dev/null +++ b/Win32/Kernel32.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace Yaulw.Win32 +{ + /// + /// Kernel32.dll Entry Points * http://pinvoke.net/ * + /// + public static class Kernel32 + { + [DllImport("kernel32.dll")] + extern public static bool AttachConsole(int dwProcessId); + + [DllImport("kernel32.dll")] + extern public static IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, [In] StringBuilder lpName); + + [DllImport("kernel32.dll")] + extern public static bool CloseHandle(IntPtr hHandle); + + [DllImport("kernel32.dll")] + extern public static bool FreeConsole(); + + [DllImport("kernel32.dll")] + extern public static int GetLastError(); + + [DllImport("kernel32.dll")] + extern public static uint GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder retVal, uint nSize, string lpFileName); + + [DllImport("kernel32.dll")] + extern public static IntPtr GetProcAddress(IntPtr hModule, String procname); + + [DllImport("kernel32.dll")] + extern public static int GetProcessId(IntPtr hProcess); + + [DllImport("kernel32.dll")] + extern public static uint GlobalAddAtom(string lpString); + + [DllImport("kernel32.dll")] + extern public static uint GlobalGetAtomName(uint nAtom, [Out] StringBuilder lpBuffer, int nSize); + + [DllImport("kernel32.dll")] + extern public static uint GlobalDeleteAtom(uint nAtom); + + [DllImport("kernel32.dll")] + extern public static IntPtr LoadLibrary(String dllname); + + [DllImport("kernel32.dll")] + extern public static IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); + + [DllImport("kernel32.dll")] + extern public static bool SetEvent(IntPtr hEvent); + + [DllImport("kernel32.dll")] + extern public static bool SetProcessDEPPolicy(int dwFlags); + + [DllImport("kernel32.dll")] + extern public static int WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); + + [DllImport("kernel32.dll")] + extern public static bool WritePrivateProfileString(string lpAppName, string lpKeyName, string lpString, string lpFileName); + + [DllImport("kernel32.dll", SetLastError = true)] + extern public static int GetShortPathName(string path, [Out] StringBuilder shortPath, int shortPathLength); + + [DllImport("kernel32.dll", SetLastError = true)] + extern public static int GetLongPathName(string path, [Out] StringBuilder longPath, int longPathLength); + } +} diff --git a/Win32/Shell32.cs b/Win32/Shell32.cs new file mode 100644 index 0000000..3bae995 --- /dev/null +++ b/Win32/Shell32.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace Yaulw.Win32 +{ + /// + /// Shell32.dll Entry Points * http://pinvoke.net/ * + /// + public static class Shell32 + { + [DllImport("Shlwapi.dll")] + extern public static uint AssocQueryString(Definitions.AssocF flags, Definitions.AssocStr str, string pszAssoc, string pszExtra, [Out] StringBuilder pszOut, [In][Out] ref uint pcchOut); + + [DllImport("shell32.dll")] + extern public static IntPtr FindExecutable(string lfFile, string lpDirectory, [Out] StringBuilder lpResult); + + [DllImport("shell32.dll")] + extern public static IntPtr ShellExecute(IntPtr hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd); + + [DllImport("shell32.dll")] + extern public static bool ShellExecuteEx(ref Structures.ShellExecuteInfo lpExecInfo); + + [DllImport("shell32.dll")] + extern public static long SHGetFolderPath(IntPtr hwndOwner, int nFolder, IntPtr hToken, int dwFlags, [Out] StringBuilder pszPath); + } +} diff --git a/Win32/Structures.cs b/Win32/Structures.cs new file mode 100644 index 0000000..6c4f9d1 --- /dev/null +++ b/Win32/Structures.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace Yaulw.Win32 +{ + /// + /// Win32 Structs + /// + public static class Structures + { + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + public static RECT FromXYWH(int x, int y, int width, int height) + { + RECT rect = new RECT(); + rect.left = x; + rect.top = y; + rect.right = x + width; + rect.bottom = y + height; + return rect; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SIZE + { + public int cx; + public int cy; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ShellExecuteInfo + { + public int cbSize; + public uint fMask; + public IntPtr hwnd; + public string lpVerb; + public string lpFile; + public string lpParameters; + public string lpDirectory; + public uint nShow; + public IntPtr hInstApp; + public IntPtr lpIDList; + public string lpClass; + public IntPtr hkeyClass; + public uint dwHotKey; + public IntPtr hIcon_Monitor; // union DUMMYUNIONNAME + public IntPtr hProcess; + } + + [StructLayout(LayoutKind.Explicit, Size = 28)] + public struct INPUT + { + [FieldOffset(0)] + public uint type; + [FieldOffset(4)] + public KEYBDINPUT ki; + }; + + [StructLayout(LayoutKind.Sequential)] + public struct KEYBDINPUT + { + public ushort wVk; + public ushort wScan; + public uint dwFlags; + public long time; + public uint dwExtraInfo; + }; + + [StructLayout(LayoutKind.Sequential)] + public struct MONITORINFOEX + { + public int cbSize; + public RECT rcMonitor; + public RECT rcWork; + public int dwFlags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string szDeviceName; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct PAINTSTRUCT + { + public IntPtr hdc; + public int fErase; + public RECT rcPaint; + public int fRestore; + public int fIncUpdate; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] rgbReserved; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NCCALCSIZE_PARAMS + { + public RECT rgrc0, rgrc1, rgrc2; + public IntPtr lppos; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SERVICE_STATUS_PROCESS + { + public int dwServiceType; + public int dwCurrentState; + public int dwControlsAccepted; + public int dwWin32ExitCode; + public int dwServiceSpecificExitCode; + public int dwCheckPoint; + public int dwWaitHint; + public int dwProcessId; + public int dwServiceFlags; + } + + [StructLayout(LayoutKind.Sequential)] + public struct FileDescriptorSet + { + // how many are set? + public int Count; + // an array of Socket handles + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxCount)] + public IntPtr[] Array; + + public static readonly int Size = Marshal.SizeOf(typeof(FileDescriptorSet)); + public static readonly FileDescriptorSet Empty = new FileDescriptorSet(0); + public const int MaxCount = 64; + + public FileDescriptorSet(int count) + { + Count = count; + Array = count == 0 ? null : new IntPtr[MaxCount]; + } + } + + /// + /// Structure used in select() call, taken from the BSD file sys/time.h. + /// + [StructLayout(LayoutKind.Sequential)] + public struct TimeValue + { + public int Seconds; // seconds + public int Microseconds; // and microseconds + } + + [StructLayout(LayoutKind.Sequential)] + public struct WSAData + { + public short wVersion; + public short wHighVersion; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)] + public string szDescription; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)] + public string szSystemStatus; + public short iMaxSockets; + public short iMaxUdpDg; + public int lpVendorInfo; + } + } +} diff --git a/Win32/User32.cs b/Win32/User32.cs new file mode 100644 index 0000000..4fb4213 --- /dev/null +++ b/Win32/User32.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace Yaulw.Win32 +{ + /// + /// User32.dll Entry Points * http://pinvoke.net/ * + /// + public static class User32 + { + // Arg for SetWindowsHookEx() + public delegate int WindowsHookProc(int nCode, IntPtr wParam, IntPtr lParam); + + // Arg for EnumWindows (EnumWindows Callback) + public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + [DllImport("user32.dll")] + extern public static IntPtr BeginPaint(IntPtr hWnd, ref Structures.PAINTSTRUCT paintStruct); + + [DllImport("user32.dll")] + extern public static int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll")] + extern public static bool EmptyClipboard(); + + [DllImport("user32.dll")] + extern public static bool EndPaint(IntPtr hWnd, ref Structures.PAINTSTRUCT paintStruct); + + [DllImport("User32.dll")] + extern public static bool EnumChildWindows(IntPtr hParent, Delegate lpEnumFunc, IntPtr lParam); + + [DllImport("User32.dll")] + extern public static bool EnumWindows(Delegate lpEnumFunc, IntPtr lParam); + + [DllImport("User32.dll")] + extern public static IntPtr FindWindow(string lpClassName, string lpWindowName); + + [DllImport("user32.dll")] + extern public static IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); + + /// + /// Helper FindWindowEx Function * Useful * + /// + public static IntPtr FW(IntPtr hwndParent, string lpszClass) + { + return FindWindowEx(hwndParent, IntPtr.Zero, lpszClass, String.Empty); + } + + [DllImport("user32.dll")] + extern public static int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); + + [DllImport("user32.dll")] + extern public static bool GetClientRect(IntPtr hWnd, out Structures.RECT rect); + + [DllImport("user32.dll")] + extern public static int GetClipboardSequenceNumber(); + + [DllImport("user32.dll")] + extern public static IntPtr GetDC(IntPtr ptr); + + [DllImport("user32.dll")] + extern public static IntPtr GetDCEx(IntPtr hWnd, IntPtr hrgnClip, int flags); + + [DllImport("user32.dll")] + extern public static IntPtr GetDesktopWindow(); + + [DllImport("user32.dll")] + extern public static IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem); + + [DllImport("user32.dll")] + extern public static uint GetDlgItemText(IntPtr hDlg, int nIDDlgItem, [Out] StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll")] + extern public static IntPtr GetFocus(); + + [DllImport("user32.dll")] + extern public static IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + extern public static short GetKeyState(Definitions.VK vKey); + + [DllImport("user32.dll")] + extern public static bool GetMonitorInfo(IntPtr hMonitor, ref Structures.MONITORINFOEX monitorinfo); + + [DllImport("user32.dll")] + extern public static IntPtr GetParent(IntPtr hWnd); + + [DllImport("user32.dll")] + extern public static int GetSystemMetrics(int index); + + [DllImport("user32.dll")] + extern public static IntPtr GetTopWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + extern public static IntPtr GetWindowDC(IntPtr hWnd); + + [DllImport("user32.dll")] + extern public static long GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll")] + extern public static bool GetWindowRect(IntPtr hWnd, out Structures.RECT rect); + + [DllImport("user32.dll")] + extern public static int GetWindowText(IntPtr hWnd, StringBuilder text, int count); + + [DllImport("user32.dll")] + extern public static int GetWindowTextLength(IntPtr hWnd); + + [DllImport("User32.dll")] + extern public static int GetWindowThreadProcessId(IntPtr hWnd, ref int lpdwProcessId); + + [DllImport("user32.dll")] + extern public static IntPtr MonitorFromRect(ref Structures.RECT rect, int dwFlags); + + [DllImport("user32.dll")] + extern public static void mouse_event(Definitions.MouseEvent dwMouseEventFlags, long dx, long dy, long cButtons, long dwExtraInfo); + + [DllImport("user32.dll")] + extern public static bool IsWindow(IntPtr hwnd); + + [DllImport("user32.dll")] + extern public static bool IsWindowEnabled(IntPtr hwnd); + + [DllImport("User32.dll")] + extern public static bool IsWindowVisible(IntPtr hwnd); + + [DllImport("user32.dll")] + public static extern bool LockWorkStation(); + + [DllImport("User32.dll")] + extern public static bool PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll")] + extern public static uint RegisterWindowMessage(string lpString); + + [DllImport("user32.dll")] + extern public static bool ReleaseCapture(); + + [DllImport("user32.dll")] + extern public static IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc); + + [DllImport("user32.dll")] + extern public static uint SendInput(uint nInputs, ref Structures.INPUT pInputs, int cbSize); + + [DllImport("User32.dll")] + extern public static int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll")] + extern public static IntPtr SetActiveWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + extern public static IntPtr SetCapture(IntPtr hWnd); + + [DllImport("user32.dll")] + extern public static IntPtr SetFocus(IntPtr hwnd); + + [DllImport("user32.dll")] + extern public static int SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + extern public static bool SetLayeredWindowAttributes(IntPtr hwnd, Int32 crKey, byte bAlpha, uint dwFlags); + + [DllImport("user32.dll")] + extern public static IntPtr SetParent(IntPtr child, IntPtr newParent); + + [DllImport("user32.dll")] + extern public static int SetWindowsHookEx(int idHook, WindowsHookProc lpfn, IntPtr hInstance, int threadId); + + [DllImport("user32.dll")] + extern public static long SetWindowLong(IntPtr hWnd, int nIndex, long dwNewLong); + + [DllImport("user32.dll")] + extern public static bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] + extern public static int ShowScrollBar(IntPtr hWnd, int wBar, int bShow); + + [DllImport("user32.dll")] + extern public static int ShowWindow(IntPtr hwnd, int nCmdShow); + + [DllImport("user32.dll")] + extern public static bool UnhookWindowsHookEx(int idHook); + } +} diff --git a/Win32/WSock32.cs b/Win32/WSock32.cs new file mode 100644 index 0000000..8e9515e --- /dev/null +++ b/Win32/WSock32.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using System.Net.Sockets; + +namespace Yaulw.Win32 +{ + /// + /// WSock32.dll Entry Points * http://pinvoke.net/ * + /// + public static class WSock32 + { + + [DllImport("wsock32.dll", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)] + extern public static SocketError WSAStartup([In] short wVersionRequested, [Out] out Structures.WSAData lpWSAData); + + [DllImport("wsock32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + extern public static int WSACleanup(); + + [DllImport("wsock32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + extern public static int select([In] int ignoredParameter, + [In, Out] ref Structures.FileDescriptorSet readfds, + [In, Out] ref Structures.FileDescriptorSet writefds, + [In, Out] ref Structures.FileDescriptorSet exceptfds, + [In] ref Structures.TimeValue timeout); + + [DllImport("wsock32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + extern public static int select([In] int ignoredParameter, + [In, Out] ref Structures.FileDescriptorSet readfds, + [In, Out] ref Structures.FileDescriptorSet writefds, + [In, Out] ref Structures.FileDescriptorSet exceptfds, + [In] IntPtr nullTimeout); + + [DllImport("wsock32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + extern public static int select([In] int ignoredParameter, + [In, Out] ref Structures.FileDescriptorSet readfds, + [In] IntPtr ignoredA, + [In] IntPtr ignoredB, + [In] ref Structures.TimeValue timeout); + + [DllImport("wsock32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + extern public static int select([In] int ignoredParameter, + [In, Out] ref Structures.FileDescriptorSet readfds, + [In] IntPtr ignoredA, + [In] IntPtr ignoredB, + [In] IntPtr nullTimeout); + + [DllImport("wsock32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + extern public static int select([In] int ignoredParameter, + [In] IntPtr ignoredA, + [In, Out] ref Structures.FileDescriptorSet writefds, + [In] IntPtr ignoredB, + [In] ref Structures.TimeValue timeout); + + [DllImport("wsock32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + extern public static int select([In] int ignoredParameter, + [In] IntPtr ignoredA, + [In, Out] ref Structures.FileDescriptorSet writefds, + [In] IntPtr ignoredB, + [In] IntPtr nullTimeout); + + [DllImport("wsock32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + extern public static int select([In] int ignoredParameter, + [In] IntPtr ignoredA, + [In] IntPtr ignoredB, + [In, Out] ref Structures.FileDescriptorSet exceptfds, + [In] ref Structures.TimeValue timeout); + + [DllImport("wsock32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + extern public static int select([In] int ignoredParameter, + [In] IntPtr ignoredA, + [In] IntPtr ignoredB, + [In, Out] ref Structures.FileDescriptorSet exceptfds, + [In] IntPtr nullTimeout); + + } +} diff --git a/Win32/Wininet.cs b/Win32/Wininet.cs new file mode 100644 index 0000000..5e2f79f --- /dev/null +++ b/Win32/Wininet.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace Yaulw.Win32 +{ + /// + /// User32.dll Entry Points * http://pinvoke.net/ * + /// + public static class Wininet + { + [DllImport("wininet", CharSet = CharSet.Auto)] + extern public static bool InternetGetConnectedState(ref Definitions.ConnectionStatusEnum flags, int dw); + + + } +} diff --git a/Win32/uxDwm.cs b/Win32/uxDwm.cs new file mode 100644 index 0000000..ad29978 --- /dev/null +++ b/Win32/uxDwm.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace Yaulw.Win32 +{ + /// + /// uxTheme.dll and dwmapi.dll Entry Points * http://pinvoke.net/ * + /// + public static class uxDwm + { + /// + /// Obtains a value that indicates whether Desktop Window Manager (DWM) composition is enabled + /// + /// TRUE if DWM composition is enabled; otherwise, FALSE + /// If this function succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code + [DllImport("dwmapi.dll")] + extern public static long DwmIsCompositionEnabled(ref bool pfEnabled); + + /// + /// Reports whether the current application's user interface displays using visual styles + /// + /// TRUE, if the application has a visual style applied. FALSE otherwise + [DllImport("uxTheme.dll")] + extern public static bool IsAppThemed(); + } +} diff --git a/WinForms/ControlClickHelper.cs b/WinForms/ControlClickHelper.cs new file mode 100644 index 0000000..e20b9b6 --- /dev/null +++ b/WinForms/ControlClickHelper.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using Yaulw.Thread; +using System.Timers; + +namespace Yaulw.WinForms +{ + /// + /// Initiate ControlClickHelper with a Control that you want to implement a Single/Double Mouse Click + /// This Class helps you get accurate Single/Double Mouse Click Events + /// + public class ControlClickHelper + { + #region Public Events + + /// + /// Single Mouse Click Delegate for subscribing to SingleMouseClick Events + /// + /// MouseEventArgs + public delegate void SingleMouseClick(MouseEventArgs e); + + /// + /// Double Mouse Click Delegate for subscribing to DoubleMouseClick Events + /// + /// MouseEventArgs + public delegate void DoubleMouseClick(MouseEventArgs e); + + /// + /// Left Mouse Click Event + /// + public event SingleMouseClick LeftMouseClick; + + /// + /// Right Mouse Click Event + /// + public event SingleMouseClick RightMouseClick; + + /// + /// Left Mouse Double Click Event + /// + public event DoubleMouseClick LeftMouseDoubleClick; + + /// + /// Right Mouse Double Click Event + /// + public event DoubleMouseClick RightMouseDoubleClick; + + #endregion + + #region Private Members + + private Control _Control = null; + private TTimerDisp SingleLeftClickDetectTimer = null; + private TTimerDisp SingleRightClickDetectTimer = null; + private TimeSpan _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks); + private const int _MILISECONDS_FOR_SINGLEMOUSE_CLICKEVENT_TOCOUNT = 350; + private const int _N_SECONDS_TOIGNORE_NEXT_SIGNLEMOUSE_CLICKEVENT = 2; // to avoid tripple clicks, etc... (only sends one double click) + private MouseEventArgs _LeftClickEventArgs; + private MouseEventArgs _RightClickEventArgs; + + /// + /// Returns true if enough time since _LastFiredEvent has passed + /// + private bool EnoughTimeSinceLastEventHasElapsed + { + get + { + return (DateTime.Now.Ticks - _LastFiredEvent.Ticks) >= (TimeSpan.FromSeconds(_N_SECONDS_TOIGNORE_NEXT_SIGNLEMOUSE_CLICKEVENT).Ticks); + } + } + + #endregion + + #region Construction + + /// + /// Initiate ControlClickHelper with a Control that you want to implement a Single/Double Mouse Click + /// This Class helps you get accurate Single/Double Mouse Click Events + /// + /// a control for which you want to implement Single/Double Mouse Click + public ControlClickHelper(Control control) + { + this._Control = control; + this.SingleLeftClickDetectTimer = new TTimerDisp(new ElapsedEventHandler(RealSingleLeftClickDetectTimer_ElapsedEventHandler), _MILISECONDS_FOR_SINGLEMOUSE_CLICKEVENT_TOCOUNT); + this.SingleRightClickDetectTimer = new TTimerDisp(new ElapsedEventHandler(RealSingleRightClickDetectTimer_ElapsedEventHandler), _MILISECONDS_FOR_SINGLEMOUSE_CLICKEVENT_TOCOUNT); + + // Add Event Handlers + control.MouseClick += new MouseEventHandler(control_MouseClick); + control.MouseDoubleClick += new MouseEventHandler(control_MouseDoubleClick); + } + + #endregion + + #region Click Event Handlers + + /// + /// Called by Control DoubleClick Event, We filter for only the left/right mouse double-click, + /// event and fire event when neccessary + /// + /// control + /// MouseEventArgs + private void control_MouseDoubleClick(object sender, MouseEventArgs e) + { + MouseEventArgs args = (MouseEventArgs)e; + if (args.Button == MouseButtons.Left) + { + SingleLeftClickDetectTimer.Stop(); + if (LeftMouseDoubleClick != null && EnoughTimeSinceLastEventHasElapsed) + { + _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks); + LeftMouseDoubleClick(new MouseEventArgs(MouseButtons.Left, 2, args.X, args.Y, args.Delta)); + } + } + else if (args.Button == MouseButtons.Right) + { + SingleRightClickDetectTimer.Stop(); + if (RightMouseDoubleClick != null && EnoughTimeSinceLastEventHasElapsed) + { + _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks); + RightMouseDoubleClick(new MouseEventArgs(MouseButtons.Right, 2, args.X, args.Y, args.Delta)); + } + } + } + + /// + // Called by NotifyIcon Click Event, We filter for only the left mouse click, + /// event and fire event when neccessary + /// + /// control + /// MouseEventArgs + private void control_MouseClick(object sender, MouseEventArgs e) + { + MouseEventArgs args = (MouseEventArgs)e; + if (args.Button == MouseButtons.Left && EnoughTimeSinceLastEventHasElapsed) + { + SingleLeftClickDetectTimer.Start(); // Start Single Click Detect Timer + _LeftClickEventArgs = e; + } + else if (args.Button == MouseButtons.Right && EnoughTimeSinceLastEventHasElapsed) + { + SingleRightClickDetectTimer.Start(); // Start Single Click Detect Timer + _RightClickEventArgs = e; + } + } + + /// + /// Used to detect ONLY Single Left Clicks, since a single-click and then a double-click fires, + /// we want to ignore the first click,and first see if a double-click comes in, if so, ignore + /// the single click, otherwise send it. + /// + /// control + /// MouseEventArgs + private void RealSingleLeftClickDetectTimer_ElapsedEventHandler(object sender, ElapsedEventArgs e) + { + SingleLeftClickDetectTimer.Stop(); + if (LeftMouseClick != null) + { + _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks); + LeftMouseClick(new MouseEventArgs(MouseButtons.Right, 1, _LeftClickEventArgs.X, _LeftClickEventArgs.Y, _LeftClickEventArgs.Delta)); + } + } + + /// + /// Used to detect ONLY Single Right Clicks, since a single-click and then a double-click fires, + /// we want to ignore the first click,and first see if a double-click comes in, if so, ignore + /// the single click, otherwise send it. + /// + /// control + /// MouseEventArgs + private void RealSingleRightClickDetectTimer_ElapsedEventHandler(object sender, ElapsedEventArgs e) + { + SingleRightClickDetectTimer.Stop(); + if (RightMouseClick != null) + { + _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks); + RightMouseClick(new MouseEventArgs(MouseButtons.Right, 1, _RightClickEventArgs.X, _RightClickEventArgs.Y, _RightClickEventArgs.Delta)); + } + } + + #endregion + } +} diff --git a/WinForms/MDIHelper.cs b/WinForms/MDIHelper.cs new file mode 100644 index 0000000..f6c4929 --- /dev/null +++ b/WinForms/MDIHelper.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace Yaulw.WinForms +{ + /// + /// Helper functions for WinForms MDI Layout + /// + public static class MDIHelper + { + /// + /// Use this function to retrive the actual MDIClient Object from + /// an MDIContainer Form + /// + /// Window form that is a valid MDIContainter + /// Valid MdiClient object if found, null otherwise + public static MdiClient GetMDIClientObj(Form mdiFormContainer) + { + if (mdiFormContainer.IsMdiContainer) + { + for (int i = 0; i < mdiFormContainer.Controls.Count; ++i) + { + MdiClient mdiClient = mdiFormContainer.Controls[i] as MdiClient; + if (mdiClient != null) + return mdiClient; + } + } + return null; + } + + /// + /// Closes all MDI Children for the specified MDI Form Container + /// + /// Window form that is a valid MDIContainter + public static void CloseAllChildWindows(Form mdiFormContainer) + { + if (mdiFormContainer.IsMdiContainer) + { + if (mdiFormContainer.MdiChildren.Length > 0) + { + Form[] form = mdiFormContainer.MdiChildren; + foreach (Form f in form) + { + f.Close(); + } + } + } + } + + /// + /// Changes the Child MDI Window Layout for the specified MDI Form Containter + /// + /// Window form that is a valid MDIContainter + /// Specify the MdiLaoyt to use + public static void HandleChildWindowsLayout(Form mdiFormContainer, MdiLayout pLayout) + { + if (!mdiFormContainer.IsMdiContainer) + throw new InvalidOperationException("mdiFormContainter not and MDI Container"); + + // We don't handle Vertical * we are done * + if (pLayout == MdiLayout.TileVertical) + return; + + // Arrange Icon works fine with MDI + if (pLayout == MdiLayout.ArrangeIcons) + { + mdiFormContainer.LayoutMdi(pLayout); + return; + } + + //// + // For Some Reason *YTBD* Cascade and Horizontal MDI Layout stopped working, + // So now we just do the same logic manually and don't use the build in functions + // provided by the MDI + //// + if (pLayout == MdiLayout.Cascade) + { + // Incr. Consts + const int N_INCR_TOP = 30; + const int N_INCR_LEFT = 18; + + Form ActiveMDI = mdiFormContainer.ActiveMdiChild; + if (ActiveMDI != null) + { + int nTop = -1 * N_INCR_TOP; + int nLeft = -1 * N_INCR_LEFT; + + // By Default, Cascade windows increases the size of the Form + // to a percentage of available with on the screen. any screen size > 800 + // width we'll use the calculated width + MdiClient client = GetMDIClientObj(mdiFormContainer); + int nCalculatedWidth = (client.Width / 100) * 80; + int nCalculatedHeight = (client.Height / 100) * 60; + + foreach (Form child in mdiFormContainer.MdiChildren) + { + if (child != ActiveMDI) + { + // Incr. nTop N' nLeft + nTop = nTop + N_INCR_TOP; + nLeft = nLeft + N_INCR_LEFT; + + // Position Childe + child.Top = nTop; + child.Left = nLeft; + child.Height = (nCalculatedHeight > child.MinimumSize.Height) ? nCalculatedHeight : child.MinimumSize.Height; + child.Width = (nCalculatedWidth > child.MinimumSize.Width) ? nCalculatedWidth : child.MinimumSize.Width; + child.Activate(); + } + } + + // Position Active MDI + ActiveMDI.Top = nTop + N_INCR_TOP; + ActiveMDI.Left = nLeft + N_INCR_LEFT; + ActiveMDI.Height = (nCalculatedHeight > ActiveMDI.MinimumSize.Height) ? nCalculatedHeight : ActiveMDI.MinimumSize.Height; + ActiveMDI.Width = (nCalculatedWidth > ActiveMDI.MinimumSize.Width) ? nCalculatedWidth : ActiveMDI.MinimumSize.Width; + ActiveMDI.Activate(); + } + } + else if (pLayout == MdiLayout.TileHorizontal) + { + Form ActiveMDI = mdiFormContainer.ActiveMdiChild; + if (ActiveMDI != null && mdiFormContainer.MdiChildren.Length > 1) + { + MdiClient client = GetMDIClientObj(mdiFormContainer); + int nMaxHeight = client.Height; + int nMaxWidth = client.Width; + + // MDI Window Counts + int nCount = mdiFormContainer.MdiChildren.Length; + int nHalf = (nCount / 2); + + // Position the first half of the Windows + int nTop = 0; + int nCalculatedHeight = nMaxHeight / nHalf; + int nCalculatedWidth = (nMaxWidth / 2); + for (int i = 0; i < nHalf; ++i) + { + Form child = mdiFormContainer.MdiChildren[i]; + + // Title the Window + child.Top = nTop; + child.Left = 0; + + // Always set to Mins + child.Height = (child.MinimumSize.Height < nCalculatedHeight) ? nCalculatedHeight : child.MinimumSize.Height; + child.Width = (child.MinimumSize.Width < nCalculatedWidth) ? nCalculatedWidth : child.MinimumSize.Width; + + // incr. nTop + nTop = nTop + nCalculatedHeight; + } + + // Position the remaining half of the Windows + nTop = 0; + nCalculatedHeight = (nMaxHeight / (nCount - nHalf)); + for (int i = nHalf; i < nCount; ++i) + { + Form child = mdiFormContainer.MdiChildren[i]; + + // Title the Window + child.Top = nTop; + child.Left = nCalculatedWidth; + + // Always set to Mins + child.Height = (child.MinimumSize.Height < nCalculatedHeight) ? nCalculatedHeight : child.MinimumSize.Height; + child.Width = (child.MinimumSize.Width < (nCalculatedWidth - 20)) ? (nCalculatedWidth - 20) : child.MinimumSize.Width; + + // incr. nTop + nTop = nTop + nCalculatedHeight; + } + } + else if (ActiveMDI != null && mdiFormContainer.MdiChildren.Length == 1) + { + ActiveMDI.Top = 0; + ActiveMDI.Left = 0; + ActiveMDI.Height = ActiveMDI.MinimumSize.Height; + ActiveMDI.Width = ActiveMDI.MinimumSize.Width; + } + } + + } + + } +} diff --git a/WinForms/MsgBox.cs b/WinForms/MsgBox.cs new file mode 100644 index 0000000..91f1416 --- /dev/null +++ b/WinForms/MsgBox.cs @@ -0,0 +1,731 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using Yaulw.Win32; +using System.Drawing; +using System.ComponentModel; +using WPFWin = System.Windows; +using WPFWinInterop = System.Windows.Interop; +using Yaulw.Tools; + +namespace Yaulw.WinForms +{ + /// + /// MsgBox is a WindowsForms MessageBox that can be centered to the Parent + /// + public static class MsgBox + { + #region Public Properties + + /// + /// Message Box Icon to use + /// + public static Icon DefaultTitleBarIcon { get; set; } + + /// + /// Set the max number of characters to show on each line + /// + public static uint MaxNumberOfCharactersPerLine { get; set; } + + /// + /// the default max number of characters on each line + /// + public const uint DefaultNumberOfCharactersPerLine = 62; + + #endregion + + #region Public Header Properties + + /// + /// Get / Set to show a header in the Message Box + /// + public static bool ShowHeader { get; set; } + + /// + /// Get / Set the Error Header + /// + public static string MsgBox_ErrorHeader { get; set; } + + /// + /// Get / Set the Fatal Error Header + /// + public static string MsgBox_FatalErrorHeader { get; set; } + + /// + /// Get / Set the Warning Header + /// + public static string MsgBox_WarningHeader { get; set; } + + /// + /// Get / Set the Information Header + /// + public static string MsgBox_InfoHeader { get; set; } + + #endregion + + #region Public Footer Properties + + /// + /// Get / Set to show a footer in the Message Box + /// + public static bool ShowFooter { get; set; } + + /// + /// Get / Set the Error Footer + /// + public static string MsgBox_ErrorFooter { get; set; } + + /// + /// Get / Set the Fatal Error Footer + /// + public static string MsgBox_FatalErrorFooter { get; set; } + + /// + /// Get / Set the Warning Footer + /// + public static string MsgBox_WarningFooter { get; set; } + + /// + /// Get / Set the Information Footer + /// + public static string MsgBox_InfoFooter { get; set; } + + #endregion + + #region Public Title Header Properties + + /// + /// Get / Set to show a header in the Message Box Title + /// + public static bool ShowTitleHeader { get; set; } + + /// + /// Get / Set the Error Title Header + /// + public static string MsgBox_ErrorTitleHeader { get; set; } + + /// + /// Get / Set the Fatal Error Title Header + /// + public static string MsgBox_FatalErrorTitleHeader { get; set; } + + /// + /// Get / Set the Warning Title Header + /// + public static string MsgBox_WarningTitleHeader { get; set; } + + /// + /// Get / Set the Information Title Header + /// + public static string MsgBox_InfoTitleHeader { get; set; } + + #endregion + + #region Construction + + /// + /// MsgBox Construction, ShowHeader, ShowFooter, ShowTitleHeader defaulted to true + /// + static MsgBox() + { + DefaultTitleBarIcon = null; + MaxNumberOfCharactersPerLine = DefaultNumberOfCharactersPerLine; + + // Header Init + ShowHeader = true; + ShowFooter = true; + ShowTitleHeader = true; + } + + #endregion + + #region Public Static Methods + + /// + /// Shows a custom Message Box (centered to parent), with the DefaultTitleBarIcon + /// + /// Window To center Message Box Against + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult Show(WPFWin.Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons, MessageBoxIcon MsgBoxIcon) + { + if (parent != null) + { + WPFWinInterop.WindowInteropHelper interop = new WPFWinInterop.WindowInteropHelper(parent); + if(interop.Handle != IntPtr.Zero) + return Show(Yaulw.Win32.Convert.ConverthWndToIWin32Window(interop.Handle), Title, Text, MsgBoxButtons, MsgBoxIcon, DefaultTitleBarIcon); + } + return Show(Functions.GetDestopWindow(), Title, Text, MsgBoxButtons, MsgBoxIcon, DefaultTitleBarIcon); + } + + /// + /// Shows a custom Message Box (centered to parent), with the DefaultTitleBarIcon + /// + /// Window To center Message Box Against + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult Show(IWin32Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons, MessageBoxIcon MsgBoxIcon) + { + return Show(parent, Title, Text, MsgBoxButtons, MsgBoxIcon, DefaultTitleBarIcon); + } + + /// + /// Shows a custom Message Box (centered to Desktop), with the DefaultTitleBarIcon + /// + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult Show(String Title, String Text, MessageBoxButtons MsgBoxButtons, MessageBoxIcon MsgBoxIcon) + { + return Show(Functions.GetDestopWindow(), Title, Text, MsgBoxButtons, MsgBoxIcon, DefaultTitleBarIcon); + } + + #endregion + + #region Public Static Methods Extended + + /// + /// Shows Warning MessageBox (WPF) + /// + /// Window To center Message Box Against + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowWarning(WPFWin.Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + return Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Exclamation); + } + + /// + /// Shows Warning MessageBox (WinForms) + /// + /// Window To center Message Box Against + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowWarning(IWin32Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + return Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Exclamation); + } + + /// + /// Shows Warning MessageBox (Desktop) + /// + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowWarning(String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + return Show(Title, Text, MsgBoxButtons, MessageBoxIcon.Exclamation); + } + + /// + /// Shows Fatal Error MessageBox (WPF) + /// + /// Window To center Message Box Against + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowFatalError(WPFWin.Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + _ShowFatal = true; + DialogResult dr = Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Error); + _ShowFatal = false; + return dr; + } + + /// + /// Shows Fatal Error MessageBox (WinForms) + /// + /// Window To center Message Box Against + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowFatalError(IWin32Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + _ShowFatal = true; + DialogResult dr = Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Error); + _ShowFatal = false; + return dr; + } + + /// + /// Shows Fatal Error MessageBox (Desktop) + /// + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowFatalError(String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + _ShowFatal = true; + DialogResult dr = Show(Title, Text, MsgBoxButtons, MessageBoxIcon.Error); + _ShowFatal = false; + return dr; + } + + /// + /// Shows Error MessageBox (WPF) + /// + /// Window To center Message Box Against + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowError(WPFWin.Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + return Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Error); + } + + /// + /// Shows Error MessageBox (WinForms) + /// + /// Window To center Message Box Against + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowError(IWin32Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + return Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Error); + } + + /// + /// Shows Error MessageBox (Desktop) + /// + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowError(String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + return Show(Title, Text, MsgBoxButtons, MessageBoxIcon.Error); + } + + /// + /// Shows Information MessageBox (WPF) + /// + /// Window To center Message Box Against + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowInfo(WPFWin.Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + return Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Information); + } + + /// + /// Shows Information MessageBox (WinForms) + /// + /// Window To center Message Box Against + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowInfo(IWin32Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + return Show(parent, Title, Text, MsgBoxButtons, MessageBoxIcon.Information); + } + + /// + /// Shows Information MessageBox (Desktop) + /// + /// Title to show + /// Text to show + /// buttons to show + /// icon to show + /// the result of the Message Box + public static DialogResult ShowInfo(String Title, String Text, MessageBoxButtons MsgBoxButtons = MessageBoxButtons.OK) + { + return Show(Title, Text, MsgBoxButtons, MessageBoxIcon.Information); + } + + #endregion + + #region Private Static Members + + private static User32.WindowsHookProc _hookProcDelegate = null; + private static int _hHook = 0; + private static string _title = null; + private static string _msg = null; + private static IntPtr _hIcon = IntPtr.Zero; + private static bool _IsDesktopOwner = false; + private static bool _ShowFatal = false; + + #endregion + + #region Private Methods + + /// + /// Delegate to make All Message Boxes Thread-Safe + /// + private delegate DialogResult ShowMsgBoxDelegate(IWin32Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons, MessageBoxIcon MsgBoxIcon, Icon TitleBarIcon); + + /// + /// *Main MessageBox Show Function* allows you to center the Message Box to the Parent * Dispatcher Safe * + /// + /// Result of Dialog + private static DialogResult Show(IWin32Window parent, String Title, String Text, MessageBoxButtons MsgBoxButtons, MessageBoxIcon MsgBoxIcon, Icon TitleBarIcon) + { + ISynchronizeInvoke InvokeObject = null; + if (parent != null && parent is ISynchronizeInvoke) + InvokeObject = (ISynchronizeInvoke)parent; + + // Invoke if we need to * Make MessageBoxes generally Thread-safe * + if ((InvokeObject != null) && InvokeObject.InvokeRequired) + { + DialogResult result = (DialogResult)InvokeObject.Invoke(new ShowMsgBoxDelegate(MsgBox.Show), new object[] { parent, Title, Text, MsgBoxButtons, MsgBoxIcon, TitleBarIcon }); + return result; + } + else + { + return MsgBox.ShowInternal(parent, Text, Title, MsgBoxButtons, MsgBoxIcon, TitleBarIcon); + } + } + + /// + /// Internal Message Box Showing Function, responsible for showing the message box, setting the hook, etc + /// + /// any IWin32Window + /// message to show + /// title to show + /// buttons to show + /// messageboxicon + /// Title bar Icon + /// + private static DialogResult ShowInternal(IWin32Window owner, string msg, string title, MessageBoxButtons btns, MessageBoxIcon icon, Icon TitleBarIcon) + { + // Create a callback delegate + _hookProcDelegate = new User32.WindowsHookProc(HookCallback); + + // Perform header/footer/title actions + HeaderFooterTitleAction(ref msg, ref title, icon); + + // Properly Format and Pad the Message + MsgTextPaddingAndFormatting(ref msg); + + // Remember the title & message that we'll look for. + // The hook sees *all* windows, so we need to make sure we operate on the right one. + _msg = msg; + _title = title; + + // if Owner is the Desktop Window + _IsDesktopOwner = (owner.Handle == Functions.GetDestopWindow().Handle); + + // Icon could not be present + if (TitleBarIcon != null) + _hIcon = TitleBarIcon.ToBitmap().GetHicon(); + else + _hIcon = IntPtr.Zero; + + // Set the hook. + // Suppress "GetCurrentThreadId() is deprecated" warning. + // It's documented that Thread.ManagedThreadId doesn't work with SetWindowsHookEx() +#pragma warning disable 0618 + _hHook = User32.SetWindowsHookEx(Definitions.WH_CBT, _hookProcDelegate, IntPtr.Zero, AppDomain.GetCurrentThreadId()); +#pragma warning restore 0618 + + // Pop a standard MessageBox. The hook will center it. + DialogResult rslt = DialogResult.None; + if (_IsDesktopOwner) + rslt = MessageBox.Show(_msg, _title, btns, icon); + else + rslt = MessageBox.Show(owner, _msg, _title, btns, icon); + + // Release hook, clean up (may have already occurred) + Unhook(); + + return rslt; + } + + /// + /// Responsible for adding Header / Footer / Title Information + /// + /// text to add header/footer for, if set + /// text to add title header for, if set + /// Message Icon used in MessageBox + private static void HeaderFooterTitleAction(ref string msg, ref string title, MessageBoxIcon icon) + { + #region Header Action + if (ShowHeader) + { + if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_FatalErrorHeader) && _ShowFatal) + msg = MsgBox_FatalErrorHeader + "\n\n" + msg; + else if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_ErrorHeader) && !_ShowFatal) + msg = MsgBox_ErrorHeader + "\n\n" + msg; + else if (icon == MessageBoxIcon.Exclamation && !String.IsNullOrEmpty(MsgBox_WarningHeader)) + msg = MsgBox_WarningHeader + "\n\n" + msg; + else if (icon == MessageBoxIcon.Information && !String.IsNullOrEmpty(MsgBox_InfoHeader)) + msg = MsgBox_InfoHeader + "\n\n" + msg; + } + #endregion + + #region Footer Action + if (ShowFooter) + { + if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_FatalErrorFooter) && _ShowFatal) + msg = msg + "\n\n" + MsgBox_FatalErrorFooter; + else if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_ErrorFooter) && !_ShowFatal) + msg = msg + "\n\n" + MsgBox_ErrorFooter; + else if (icon == MessageBoxIcon.Exclamation && !String.IsNullOrEmpty(MsgBox_WarningFooter)) + msg = msg + "\n\n" + MsgBox_WarningFooter; + else if (icon == MessageBoxIcon.Information && !String.IsNullOrEmpty(MsgBox_InfoFooter)) + msg = msg + "\n\n" + MsgBox_InfoFooter; + } + #endregion + + #region Title Header + if (ShowTitleHeader) + { + if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_FatalErrorTitleHeader) && _ShowFatal) + title = MsgBox_FatalErrorTitleHeader + ((!String.IsNullOrEmpty(title)) ? (" (" + title + ")") : ""); + else if (icon == MessageBoxIcon.Error && !String.IsNullOrEmpty(MsgBox_ErrorTitleHeader) && !_ShowFatal) + title = MsgBox_ErrorTitleHeader + ((!String.IsNullOrEmpty(title)) ? (" (" + title + ")") : ""); + else if (icon == MessageBoxIcon.Exclamation && !String.IsNullOrEmpty(MsgBox_WarningTitleHeader)) + title = MsgBox_WarningTitleHeader + ((!String.IsNullOrEmpty(title)) ? (" (" + title + ")") : ""); + else if (icon == MessageBoxIcon.Information && !String.IsNullOrEmpty(MsgBox_InfoTitleHeader)) + title = MsgBox_InfoTitleHeader + ((!String.IsNullOrEmpty(title)) ? (" (" + title + ")") : ""); + } + #endregion + } + + /// + /// Perform Message Text padding and Text Wrapping + /// + /// the Padded and Text Wrapped Message + private static void MsgTextPaddingAndFormatting(ref string msg) + { + // Stripe last \n, if exists + if (!String.IsNullOrEmpty(msg) && (msg.Length > 0) && (msg[msg.Length - 1] == '\n')) + msg = msg.Remove(msg.Length - 1); + + // Make sure the text looks good, by using padding + if (!String.IsNullOrEmpty(msg) && (msg.Length > 0) && (msg.Length < MaxNumberOfCharactersPerLine)) + { + string[] lines = msg.Split('\n'); + StringBuilder sb = new StringBuilder(); + foreach (string line in lines) + { + sb.Append(line.PadRight((int)MaxNumberOfCharactersPerLine)); + sb.Append("\n"); + } + msg = sb.ToString(); + } + else if (!String.IsNullOrEmpty(msg) && (msg.Length > 0) && (msg.Length > MaxNumberOfCharactersPerLine)) + { + // Incredible and amazing Padding code + string[] lines = msg.Split('\n'); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < lines.Length; ++i) + { + if (lines[i].Length < MaxNumberOfCharactersPerLine) + { + sb.Append(lines[i].PadRight((int)MaxNumberOfCharactersPerLine)); + sb.Append("\n"); + } + else if (lines[i].Length == MaxNumberOfCharactersPerLine) + { + sb.Append(lines[i]); + sb.Append("\n"); + } + else if (lines[i].Length > MaxNumberOfCharactersPerLine) + { + // Split up all the SubLines into a List + List nSubLinesList = new List(); + for (int j = 0; j < lines[i].Length; j = j + (int)MaxNumberOfCharactersPerLine) + { + string line = lines[i].Substring(j, ((lines[i].Length - j) > (int)MaxNumberOfCharactersPerLine) ? (int)MaxNumberOfCharactersPerLine : (lines[i].Length - j)); + nSubLinesList.Add(line); + } + + // Perform proper Text Wrapping on all Sub Lines + for (int j = 0; j < nSubLinesList.Count; ++j) + { + string line1 = nSubLinesList[j]; + string line2 = (j + 1 < nSubLinesList.Count) ? nSubLinesList[j + 1] : String.Empty; + if (!String.IsNullOrEmpty(line2)) + { + if (StringTool.StringEndsWithLetter(line1) && StringTool.StringStartsWithLetter(line2)) + { + string word1 = StringTool.StringFetchLastWord(line1); + string word2 = StringTool.StringFetchFirstWord(line2); + string word = word1 + word2; + + // Use Text Wrapping to make sure word is wrapped correctly + if (word.Length < (int)MaxNumberOfCharactersPerLine) + { + StringTool.StringStripeLastWord(ref line1); + StringTool.StringStripeFirstWord(ref line2); + + // Now perform the proper corrections to the actual list + nSubLinesList[j] = line1.PadRight((int)MaxNumberOfCharactersPerLine); + nSubLinesList[j + 1] = word + line2; + + // Adjust lines + string toMoveDown = ""; + for (int z = j + 1; z < nSubLinesList.Count; ++z) + { + // First Append the Move down + if (!String.IsNullOrEmpty(toMoveDown)) + { + nSubLinesList[z] = toMoveDown + nSubLinesList[z]; + toMoveDown = ""; + } + + // Check Char Limit * make adjustment as needed * + if (nSubLinesList[z].Length > MaxNumberOfCharactersPerLine) + { + toMoveDown = nSubLinesList[z].Substring((int)MaxNumberOfCharactersPerLine); + nSubLinesList[z] = nSubLinesList[z].Substring(0, (int)MaxNumberOfCharactersPerLine); + } + } + + // Add the dangling Move Down as a new Line(s) + if (!String.IsNullOrEmpty(toMoveDown)) + { + for (int z = 0; z < toMoveDown.Length; z = z + (int)MaxNumberOfCharactersPerLine) + { + string line = toMoveDown.Substring(z, ((toMoveDown.Length - z) > (int)MaxNumberOfCharactersPerLine) ? (int)MaxNumberOfCharactersPerLine : (toMoveDown.Length - z)); + nSubLinesList.Add(line); + } + } + } + } + } + } + + // Iterate all subLines and add them to the main list * Padded * + foreach (string line in nSubLinesList) + { + if (line.Length == (int)MaxNumberOfCharactersPerLine) + { + sb.Append(line); + sb.Append("\n"); + } + else + { + sb.Append(line.PadRight((int)MaxNumberOfCharactersPerLine)); + sb.Append("\n"); + } + } + } + } + + // Write nicely formatted Message out + msg = sb.ToString(); + } + else + { + // do nothing, string is miracioulsy exactly correct + } + } + + /// + /// Unhook the User Window Hook + /// + private static void Unhook() + { + User32.UnhookWindowsHookEx(_hHook); + _hHook = 0; + _hookProcDelegate = null; + _msg = null; + _title = null; + } + + /// + /// Callback for the User Window Hook + /// + /// + /// wParam Passed in + /// lParam Passed in + /// the result of the next hook in the chain + private static int HookCallback(int code, IntPtr wParam, IntPtr lParam) + { + int hHook = _hHook; // Local copy for CallNextHookEx() JIC we release _hHook + + // Look for HCBT_ACTIVATE, *not* HCBT_CREATEWND: + // child controls haven't yet been created upon HCBT_CREATEWND. + if (code == Definitions.HCBT_ACTIVATE) + { + string cls = Functions.GetWindowClassName(wParam); + if (cls == "#32770") // MessageBoxes are Dialog boxes + { + string title = Functions.GetWindowText(wParam); + string msg = Functions.GetDlgItemText(wParam, 0xFFFF); // -1 aka IDC_STATIC + if ((title == _title) && (msg == _msg)) + { + // Only Center the Window, if the Owner is NOT the Desktop + if (!_IsDesktopOwner) + CenterWindowOnParent(wParam); + + Unhook(); // Release hook - we've done what we needed + + // Now we also want to set the Icon on the Dialog + if (_hIcon != IntPtr.Zero) + { + User32.SendMessage(wParam, (int)Definitions.WM.WM_SETICON, (IntPtr)1, _hIcon); + User32.SendMessage(wParam, (int)Definitions.WM.WM_SETICON, (IntPtr)0, _hIcon); + } + } + } + } + return User32.CallNextHookEx(hHook, code, wParam, lParam); + } + + /// + /// Boilerplate window-centering code. + /// Split out of HookCallback() for clarity. + /// + /// handle of child window to center + private static void CenterWindowOnParent(IntPtr hChildWnd) + { + // Get child (MessageBox) size + Structures.RECT rcChild = new Structures.RECT(); + User32.GetWindowRect(hChildWnd, out rcChild); + int cxChild = rcChild.right - rcChild.left; + int cyChild = rcChild.bottom - rcChild.top; + + // Get parent (Form) size & location + IntPtr hParent = User32.GetParent(hChildWnd); + Structures.RECT rcParent = new Structures.RECT(); + User32.GetWindowRect(hParent, out rcParent); + int cxParent = rcParent.right - rcParent.left; + int cyParent = rcParent.bottom - rcParent.top; + + // Center the MessageBox on the Form + int x = rcParent.left + (cxParent - cxChild) / 2; + int y = rcParent.top + (cyParent - cyChild) / 2; + uint uFlags = 0x15; // SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE; + User32.SetWindowPos(hChildWnd, IntPtr.Zero, x, y, 0, 0, uFlags); + } + + #endregion + } +} diff --git a/WinForms/SysTray.cs b/WinForms/SysTray.cs new file mode 100644 index 0000000..c543373 --- /dev/null +++ b/WinForms/SysTray.cs @@ -0,0 +1,408 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; +using System.Windows.Forms; +using Yaulw.Thread; +using System.Timers; + +namespace Yaulw.WinForms +{ + /// + /// Wrapper Class around .Net NotifyIcon, to make it easier to work with an System Tray Icon. + /// Use the InitializeContextMenu Callback to create the ContextMenu. + /// Subscripe to the MouseLeftClick, and MouseLeftDoubleClick event to get accurate events to handle, + /// Call Show()/Hide() to show/hide the System Tray Icon, respectively. + /// + public class SysTray : IDisposable + { + #region Public States + + /// + /// Allow caller to specify multipe states to show for the tray icon + /// + public struct TrayState + { + string nameOfState; + string toolTip; + Icon icon; + } + + // keep track of added States + private List _states = new List(); + + /// + /// Add a possible state to the collection of tray icon states + /// + /// + public void AddState(TrayState state) + { + _states.Add(state); + } + + #endregion + + #region Public Properties + + /// + /// Get/Set the Icon to show on the System Tray Notification + /// + public Icon Icon { get { return trayNotify.Icon; } set { trayNotify.Icon = value; } } + + /// + /// Get/Set ToolTip to show over System Tray Notification + /// + public string toolTip { get { return trayNotify.Text; } set { trayNotify.Text = value; } } + + /// + /// Is System Tray Notification Visible? + /// + public bool IsVisible { get { return trayNotify.Visible; } } + + #endregion + + #region Private Properties + + private NotifyIcon trayNotify { get; set; } + private ContextMenu trayMenu { get { return trayNotify.ContextMenu; } set { trayNotify.ContextMenu = value; } } + private ContextMenuStrip trayMenuStrip { get { return trayNotify.ContextMenuStrip; } set { trayNotify.ContextMenuStrip = value; } } + + #endregion + + #region Public Events + + /// + /// System Tray Left Mouse Click Delegate + /// + /// MouseEventArgs + public delegate void SingleLeftMouseClick(MouseEventArgs e); + + /// + /// Subscribe to get the Left Mouse Single Click Event + /// + public event SingleLeftMouseClick LeftMouseClick; + + /// + /// System Tray Left Mouse Double Click Delegate + /// + /// MouseEventArgs + public delegate void DoubleLeftMouseClick(MouseEventArgs e); + + /// + /// Subscribe to get the Left Mouse Double Click Event + /// + public event DoubleLeftMouseClick LeftMouseDoubleClick; + + /// + /// Initialize Context Menu Dynamically Call-back * i.e. On every Right Click * + /// Allows the Context Menu to change depending on application state + /// + /// Expected a new ContextMenuStrip to Display + /// Event Handle to handle Menu Click Events + public delegate void InitializeContextMenu(out ContextMenuStrip trayMenuStrip, out EventHandler MenuClickEventHandler); + + #endregion + + #region Private Members + + private InitializeContextMenu _ContextMenuInitializer = null; + private TTimerDisp SingleClickDetectTimer = null; + private TimeSpan _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks); + private const int _MILISECONDS_FOR_SINGLEMOUSE_CLICKEVENT_TOCOUNT = 350; + private const int _N_SECONDS_TOIGNORE_NEXT_SIGNLEMOUSE_CLICKEVENT = 2; // to avoid tripple clicks, etc... (only sends one double click) + private bool _disposed = false; + + /// + /// Returns true if enough time since _LastFiredEvent has passed + /// + private bool EnoughTimeSinceLastEventHasElapsed + { + get + { + return (DateTime.Now.Ticks - _LastFiredEvent.Ticks) >= (TimeSpan.FromSeconds(_N_SECONDS_TOIGNORE_NEXT_SIGNLEMOUSE_CLICKEVENT).Ticks); + } + } + + #endregion + + #region Construction + + /// + /// Construct a System Tray Icon (use public properties like ContextMenu,Icon,toolTip to customize further) + /// + /// Callback to initialize the Context Menu (can't be null) + /// pass in an initial ToolTip to display, defaults to "" + /// if null, defaults to systemIcons.Application + public SysTray(InitializeContextMenu ContextMenuInitializer, string toolTip = "", Icon icon = null) + { + if (ContextMenuInitializer == null) + throw new ArgumentNullException("ContextMenuInitializer can not be Null"); + + // Imp, in order to initialize the Context Menu Everytime it is needed + _ContextMenuInitializer = ContextMenuInitializer; + + // Create internal objects + this.trayNotify = new NotifyIcon(); + this.SingleClickDetectTimer = new TTimerDisp(new ElapsedEventHandler(RealSingleClickDetectTimer_ElapsedEventHandler), _MILISECONDS_FOR_SINGLEMOUSE_CLICKEVENT_TOCOUNT); + + // Add Single / Double-Click Event Handlers + trayNotify.Click += new EventHandler(trayNotify_Click); + trayNotify.DoubleClick += new EventHandler(trayNotify_DoubleClick); + trayNotify.MouseDown += new MouseEventHandler(trayNotify_MouseDown); + + // Set ToolTip + if (!String.IsNullOrEmpty(toolTip)) + this.toolTip = toolTip; + + // Set Icon + if (icon == null) + this.Icon = new Icon(SystemIcons.Application, 40, 40); + else + this.Icon = icon; + } + + /// + /// Finalizer + /// + ~SysTray() + { + Dispose(true); + } + + #endregion + + #region Click Event Handlers + + /// + /// Called by NotifyIcon DoubleClick Event, We filter for only the left mouse double-click, + /// event and fire event when neccessary + /// + /// + /// + private void trayNotify_DoubleClick(object sender, EventArgs e) + { + MouseEventArgs args = (MouseEventArgs)e; + if (args.Button == MouseButtons.Left) + { + SingleClickDetectTimer.Stop(); + if (LeftMouseDoubleClick != null && EnoughTimeSinceLastEventHasElapsed) + { + _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks); + LeftMouseDoubleClick(new MouseEventArgs(MouseButtons.Left, 2, 0, 0, 0)); + } + } + } + + /// + // Called by NotifyIcon Click Event, We filter for only the left mouse click, + /// event and fire event when neccessary + /// + /// + /// + private void trayNotify_Click(object sender, EventArgs e) + { + MouseEventArgs args = (MouseEventArgs) e; + if (args.Button == MouseButtons.Left && EnoughTimeSinceLastEventHasElapsed) + SingleClickDetectTimer.Start(); // Start Single Click Detect Timer + } + + /// + /// In order to accurately re-do a context menu, we handle MouseDown for the + /// Right-Mouse click. Mouse Down comes in before the click event, which gives + /// the caller an opportunity to handle/recreate the context menu dynamically, if needed + /// + /// + /// + void trayNotify_MouseDown(object sender, MouseEventArgs e) + { + MouseEventArgs args = (MouseEventArgs)e; + if (args.Button == MouseButtons.Right) + { + // Dynamically re-create Menu on every Right-Click + InitializeContextMenuFromScratchUsingInitializer(); + } + } + + /// + /// Used to detect ONLY Single Clicks, since a single-click and then a double-click fires, + /// we want to ignore the first click,and first see if a double-click comes in, if so, ignore + /// the single click, otherwise send it. (this is done by trayNotify_Click & transNotify_DoubleClick) + /// + /// + /// + private void RealSingleClickDetectTimer_ElapsedEventHandler(object sender, ElapsedEventArgs e) + { + SingleClickDetectTimer.Stop(); + if (LeftMouseClick != null) + { + _LastFiredEvent = new TimeSpan(DateTime.Now.Ticks); + LeftMouseClick(new MouseEventArgs(MouseButtons.Left, 1, 0, 0, 0)); + } + } + + #endregion + + #region Private Helpers + + /// + /// (Re)Initialize Context Menu from scratch, allows us to build different menu's + /// depending on the application state * External Caller responsible for passing in + /// the context Menu and Click Event Handler * + /// + private void InitializeContextMenuFromScratchUsingInitializer() + { + if (_ContextMenuInitializer != null) + { + ContextMenuStrip menuStrip = null; + EventHandler clickHandler = null; + + // Call Outside Caller's Initialize Context menu Function + _ContextMenuInitializer(out menuStrip, out clickHandler); + + // Set up the Click Event + if (menuStrip != null && clickHandler != null) + AssignClickEventHandlerToAllMenuItems(menuStrip, clickHandler); + + // Assign New Context Menu + trayMenuStrip = menuStrip; + } + } + + /// + /// Calls AssignClickEventHandlerToMenuItem() for each Root Menu Item + /// + /// + /// + private void AssignClickEventHandlerToAllMenuItems(ContextMenuStrip menuStrip, EventHandler clickHandler) + { + if (menuStrip != null) + { + foreach (ToolStripMenuItem item in menuStrip.Items.OfType()) + AssignClickEventHandlerToMenuItem(item, clickHandler); + } + } + + /// + /// Recursive Function, in order to assign all MenuItems as well as + /// all subsequent MenuItems, in the DropDownList, the ToolStripMI_Click + /// event Handler + /// + /// pass in a ToolStripMenuItem to asign the click handler to + /// + private void AssignClickEventHandlerToMenuItem(ToolStripMenuItem item, EventHandler clickHandler) + { + if (item.DropDownItems == null || item.DropDownItems.Count == 0) + { + if(clickHandler != null) + item.Click += clickHandler; + return; + } + else + { + foreach (ToolStripMenuItem _item in item.DropDownItems.OfType()) + AssignClickEventHandlerToMenuItem(_item, clickHandler); + } + } + + #endregion + + #region Show N' Hide + + /// + /// Show the System Tray Icon + /// + public void Show() + { + // Create Context Menu + InitializeContextMenuFromScratchUsingInitializer(); + trayNotify.Visible = true; + } + + /// + /// Hide the System Tray Icon + /// + public void Hide() + { + trayNotify.Visible = false; + } + + #endregion + + #region Public ShowBallon + + /// + /// Type of Icon to show over Ballon + /// + public enum BallonIcon + { + None, + Error, + Warning, + Info + } + + /// + /// Pops up a Ballon over the System Tray Icon + /// + /// Title to show on the Ballon Tip + /// Text to show on the Ballon Tip + /// Icon to show on the Ballon tip + /// Specify the Timeout in Seconds (System mininimum is 10 seconds) + public void ShowBallon(string BallonTipTitle, string BallonTipText, BallonIcon tipIcon = BallonIcon.None, int nTimeoutInSeconds = 10) + { + ToolTipIcon _tipIcon = ToolTipIcon.None; + switch (tipIcon) + { + case BallonIcon.Error: + _tipIcon = ToolTipIcon.Error; + break; + case BallonIcon.Info: + _tipIcon = ToolTipIcon.Info; + break; + case BallonIcon.Warning: + _tipIcon = ToolTipIcon.Warning; + break; + } + trayNotify.ShowBalloonTip((int)TimeSpan.FromSeconds(nTimeoutInSeconds).TotalMilliseconds, BallonTipTitle, BallonTipText, _tipIcon); + } + + #endregion + + #region IDisposable Members + + /// + /// Dispose the Registry Handle + /// + public void Dispose() + { + Dispose(true); + + // Use SupressFinalize in case a subclass + // of this type implements a finalizer + GC.SuppressFinalize(this); + } + + /// + /// Dispose the Registry Handle + /// + /// true, if called from within + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if (trayNotify != null) + trayNotify.Dispose(); + } + + // Indicate that the instance has been disposed. + trayNotify = null; + _disposed = true; + } + } + + #endregion + } +} diff --git a/Xml/XSerializer.cs b/Xml/XSerializer.cs new file mode 100644 index 0000000..6499063 --- /dev/null +++ b/Xml/XSerializer.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; +using System.IO; +using System.Xml; + +namespace Yaulw.Xml +{ + /// + /// Wrapper class around XMLReading/Writing (Serialization) + /// + public class XSerializer + { + #region Private Members + + private XmlSerializer _Serializer = null; + private Type _SerializerAssignedToType = null; + + #endregion + + #region * Private Initialization * Called at the begining of every function call + + /// + /// Pass in a valid XML Serializable Object in order to initialize the XML Serializer + /// ~Only newes a NEW Serializer object if the underlying Type for this class changed + /// + /// Thrown if an invalid XmlObject is passed in + /// Thrown if by the end of this function, no Serializer Object Exists + private void InitializeSerializer() + { + // Create the Serializer Object, if Needed + bool bCreateSerializer = false; + if (_Serializer == null) + bCreateSerializer = true; + else if (_SerializerAssignedToType != typeof(T)) + bCreateSerializer = true; + + if (bCreateSerializer) + { + _Serializer = null; + _SerializerAssignedToType = null; + + // Create Serializer Obj + _SerializerAssignedToType = typeof(T); + _Serializer = new XmlSerializer(typeof(T)); + } + + // Something is wrong, if Serializer, is not created at this point + if (_Serializer == null) + throw new OutOfMemoryException(); + } + + #endregion + + #region Construction + + public XSerializer() { } + + #endregion + + #region Public Methods + + /// + /// Reads in an XML Object from a String + /// + /// XMLSerializable Type + /// an xml string to read + /// Deserialized Object + public T ReadFromString(string xml) + { + InitializeSerializer(); + + // Deserialize the Resource and return the Object + using (StringReader reader = new StringReader(xml)) + { + T retVal = (T)_Serializer.Deserialize(reader); + return retVal; + } + } + + /// + /// Reads in an XML Object from a String + /// + /// XMLSerializable Type + /// an xml stream to read + /// Deserialized Object + public T ReadFromStream(Stream xml) + { + InitializeSerializer(); + + // Deserialize the Resource and return the Object + using (StreamReader reader = new StreamReader(xml)) + { + T retVal = (T)_Serializer.Deserialize(reader); + return retVal; + } + } + + /// + /// Reads in an XML Object from a Stream (Resource) + /// + /// XMLSerializable Type + /// A Stream to read from + /// Deserialized Object + public T ReadFromResource(Stream ResourceStream) + { + InitializeSerializer(); + + // Deserialize the Resource and return the Object + using (TextReader reader = new StreamReader(ResourceStream)) + { + T retVal = (T)_Serializer.Deserialize(reader); + return retVal; + } + } + + /// + /// Reads in an XML Object from a File + /// + /// XMLSerializable Type + /// Valid Path to XML File to Deserialize + /// Deserialized Object, or null if file not found + public T ReadFromFile(string PathNFileName) + { + InitializeSerializer(); + + // Check File Existence + if (!System.IO.File.Exists(PathNFileName)) + return default(T); + + // Deserialize the File and return the Object + using (TextReader reader = new StreamReader(PathNFileName)) + { + T retVal = (T)_Serializer.Deserialize(reader); + return retVal; + } + } + + /// + /// Writes an XML Object to a String + /// + /// XMLSerializable Type + /// An XML Serializable Object, can be null to write Blank Object + /// a serialized XML Object as a string + public string WriteToString(T XmlObject) where T : new() + { + InitializeSerializer(); + + // Determine 'Blank' or Running Object + T objectToSerialize = default(T); + if (XmlObject == null) + objectToSerialize = new T(); + else + objectToSerialize = XmlObject; + + using (StringWriter writer = new StringWriter()) + { + _Serializer.Serialize(writer, objectToSerialize); + return writer.ToString(); + } + } + + /// + /// Writes an XML Object to a File + /// + /// XMLSerializable Type + /// An XML Serializable Object, can be null to write Blank Object + /// Valid Path to XML File to Serialize + public void WriteToFile(T XmlObject, string PathNFileName) where T : new() + { + InitializeSerializer(); + + // Create output directory, if not exists + if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(PathNFileName))) + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(PathNFileName)); + + // Determine 'Blank' or Running Object + T objectToSerialize = default(T); + if (XmlObject == null) + objectToSerialize = new T(); + else + objectToSerialize = XmlObject; + + // Write File; + using (TextWriter writer = new StreamWriter(PathNFileName)) + { + _Serializer.Serialize(writer, objectToSerialize); + } + } + + #endregion + } +} diff --git a/Yaulw.csproj b/Yaulw.csproj new file mode 100644 index 0000000..edcc7f8 --- /dev/null +++ b/Yaulw.csproj @@ -0,0 +1,151 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {1FA1096D-829D-4EBC-8155-E3702B680EEA} + Library + Properties + Yaulw + Yaulw + v3.5 + 512 + + + + + + + + + + + + true + full + false + ..\Target\Debug\ + TRACE;DEBUG;NET35 + prompt + 4 + + + pdbonly + true + ..\Target\Release\ + TRACE;NET35 + prompt + 4 + + + + Components\log4net.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Component + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Yaulw.csproj.vspscc b/Yaulw.csproj.vspscc new file mode 100644 index 0000000..b6d3289 --- /dev/null +++ b/Yaulw.csproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..4021946 --- /dev/null +++ b/license.txt @@ -0,0 +1,13 @@ +Copyright 2012 Ideas To Actions, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file