diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3c51781 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# To learn more about .editorconfig see https://aka.ms/editorconfigdocs + +# All files +[*] +indent_size = 4 +indent_style = space +tab_width = 4 + +[*.{xml,xsd,xsl}] +indent_size = 2 diff --git a/.github/workflows/msbuild.yml b/.github/workflows/msbuild.yml index 29b6ace..6a33d3d 100644 --- a/.github/workflows/msbuild.yml +++ b/.github/workflows/msbuild.yml @@ -16,10 +16,10 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1.0.2 + uses: microsoft/setup-msbuild@v1.1 - name: Restore NuGet packages working-directory: ${{env.GITHUB_WORKSPACE}} diff --git a/.gitignore b/.gitignore index 6e30c21..d9a1d01 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ /packages /_crashes /_releases + +*.aps +*.vcxproj.user diff --git a/ClassicParentalControl.sln b/ClassicParentalControl.sln index 276f50a..490116e 100644 --- a/ClassicParentalControl.sln +++ b/ClassicParentalControl.sln @@ -1,11 +1,20 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.779 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.33130.400 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LogonHoursService", "LogonHoursService\LogonHoursService.vcxproj", "{D6E78055-7BC4-4301-BDFC-C4DDA35E959C}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LogonHoursManager", "LogonHoursManager\LogonHoursManager.vcxproj", "{1FB09AD5-5BEA-427A-9862-6884A60AA97A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "Common\Common.vcxitems", "{2665D681-30A9-4760-9648-25AB585A19B3}" +EndProject Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + Common\Common.vcxitems*{1fb09ad5-5bea-427a-9862-6884a60aa97a}*SharedItemsImports = 4 + Common\Common.vcxitems*{2665d681-30a9-4760-9648-25ab585a19b3}*SharedItemsImports = 9 + Common\Common.vcxitems*{d6e78055-7bc4-4301-bdfc-c4dda35e959c}*SharedItemsImports = 4 + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x86 = Debug|x86 Release|x86 = Release|x86 @@ -15,6 +24,10 @@ Global {D6E78055-7BC4-4301-BDFC-C4DDA35E959C}.Debug|x86.Build.0 = Debug|Win32 {D6E78055-7BC4-4301-BDFC-C4DDA35E959C}.Release|x86.ActiveCfg = Release|Win32 {D6E78055-7BC4-4301-BDFC-C4DDA35E959C}.Release|x86.Build.0 = Release|Win32 + {1FB09AD5-5BEA-427A-9862-6884A60AA97A}.Debug|x86.ActiveCfg = Debug|Win32 + {1FB09AD5-5BEA-427A-9862-6884A60AA97A}.Debug|x86.Build.0 = Debug|Win32 + {1FB09AD5-5BEA-427A-9862-6884A60AA97A}.Release|x86.ActiveCfg = Release|Win32 + {1FB09AD5-5BEA-427A-9862-6884A60AA97A}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Common/Common.vcxitems b/Common/Common.vcxitems new file mode 100644 index 0000000..72701ee --- /dev/null +++ b/Common/Common.vcxitems @@ -0,0 +1,27 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {2665d681-30a9-4760-9648-25ab585a19b3} + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Common/Logger.cpp b/Common/Logger.cpp new file mode 100644 index 0000000..aaa3067 --- /dev/null +++ b/Common/Logger.cpp @@ -0,0 +1,105 @@ +#include "stdafx.h" +#include "Logger.h" + +#include +#include +#include +#include + +#include "version.h" + +#pragma comment(lib, "ws2_32.lib") // Needed for log4cpp if PropertyConfigurator is used + +void LogMain(const char* appname) +{ + log4cpp::Category& root = log4cpp::Category::getRoot(); + + //////////////////////////////////////// + // Configure log4cpp + //////////////////////////////////////// + char config_path[_MAX_PATH] = { 0 }; + GetModuleFileNameA(NULL, config_path, _countof(config_path)); + strcat_s(config_path, ".log4cpp"); + try + { + log4cpp::PropertyConfigurator::configure(config_path); + } + catch (const log4cpp::ConfigureFailure& /*ex*/) + { + config_path[0] = 0; + + // Default configuration for log4cpp +#ifdef _DEBUG + root.setPriority(log4cpp::Priority::DEBUG); +#else + root.setPriority(log4cpp::Priority::INFO); +#endif + + } + + const log4cpp::AppenderSet& set = root.getAllAppenders(); + if (set.empty()) // if no appenders are specified in the config file ... + { + char log_path_format[MAX_PATH]; + ExpandEnvironmentStringsA("%TEMP%\\%s.log", log_path_format, _countof(log_path_format)); + + char log_path[MAX_PATH]; + if (appname) + sprintf_s(log_path, log_path_format, appname); + else + { + char module_path[_MAX_PATH] = { 0 }; + GetModuleFileNameA(NULL, module_path, _countof(module_path)); + + char filename[MAX_PATH] = { 0 }; + _splitpath_s(module_path, NULL, 0, NULL, 0, filename, _countof(filename), NULL, 0); + sprintf_s(log_path, log_path_format, filename); + } + +#if 1 + if (log4cpp::Appender* const appender = new log4cpp::DailyRollingFileAppender("logfile", log_path, 14)) +#else + if (log4cpp::Appender* const appender = new log4cpp::RollingFileAppender("logfile", log_path, 10*1024*1024, 10)) +//#else + if (log4cpp::Appender* const appender = new log4cpp::FileAppender("logfile", log_path)) +#endif + { + log4cpp::PatternLayout* const layout = new log4cpp::PatternLayout(); + // Refer to http://www.cplusplus.com/reference/ctime/strftime/ for format specification + layout->setConversionPattern("%d{%Y.%m.%d %H:%M:%S,%l} [%p] [%t] %c: %m%n"); + appender->setLayout(layout); + root.addAppender(appender); + } + if (log4cpp::Appender* const appender = new log4cpp::Win32DebugAppender("debugger")) + { + log4cpp::PatternLayout* const layout = new log4cpp::PatternLayout(); + // Refer to http://www.cplusplus.com/reference/ctime/strftime/ for format specification + layout->setConversionPattern("%d{%H:%M:%S,%l} [%p] [%t] %c: %m%n"); + appender->setLayout(layout); + root.addAppender(appender); + } + } + + LOG_INFO(__func__) << "========================================"; + LOG_INFO(__func__) << "Log initialized successfully"; + if (*config_path) + LOG_INFO(__func__) << "Loaded " << config_path; + +#ifndef _CONSOLE + LOG_INFO(__func__) << "Length of command line: " << _tcslen(GetCommandLine()); +#endif + + LOG_INFO(__func__) << "Version: " << VERSION_STRING; + LOG_INFO(__func__) << "Build : " << __DATE__ << ' ' << __TIME__; +#if defined(_MSC_FULL_VER) + LOG_INFO(__func__) << "_MSC_FULL_VER: " + << _MSC_FULL_VER / 10000000 << '.' + << (_MSC_FULL_VER % 10000000) / 100000 << '.' + << _MSC_FULL_VER % 100000; +#elif defined(_MSC_VER) + LOG_INFO(__func__) << "_MSC_VER: " + << _MSC_VER / 100 << '.' + << _MSC_VER % 100; +#endif + +} diff --git a/LogonHoursService/Logger.h b/Common/Logger.h similarity index 97% rename from LogonHoursService/Logger.h rename to Common/Logger.h index 6128d78..f57981f 100644 --- a/LogonHoursService/Logger.h +++ b/Common/Logger.h @@ -49,3 +49,5 @@ inline log4cpp::Category& Logger(const void* p = NULL) { return log4cpp::Category::getRoot(); } + +void LogMain(const char* appname = nullptr); diff --git a/Common/LogonHours.cpp b/Common/LogonHours.cpp new file mode 100644 index 0000000..413d027 --- /dev/null +++ b/Common/LogonHours.cpp @@ -0,0 +1,205 @@ +#include "stdafx.h" +#include "LogonHours.h" + +#include "Logger.h" + +#pragma comment(lib, "Netapi32.lib") // NetUserGetInfo(), etc. + +EDayOfWeek& operator ++(EDayOfWeek& day) +{ + if (day < EDayOfWeek::LAST) + { + int value = static_cast(day); + day = static_cast(++value); + return day; + } + LOG_ERROR(__func__) << "Cannot increment " << int(day); + return day; +} + +LogonHours::LogonHours() +{ +} + +LogonHours::LogonHours(PBYTE usri2_logon_hours) +{ + Init(usri2_logon_hours); +} + +bool LogonHours::ApplyTo(const char* username) const +{ + const std::wstring wusername(username, username + strlen(username)); + return ApplyTo(wusername.c_str()); +} + +bool LogonHours::ApplyTo(const wchar_t* username) const +{ + // Each bit in the string represents a unique hour in the week, in Greenwich Mean Time (GMT). + // The first bit (bit 0, word 0) is Sunday, 0:00 to 0:59; the second bit (bit 1, word 0) is Sunday, 1:00 to 1:59; and so on. + // (c) https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/ns-lmaccess-user_info_1020 + BYTE usri2_logon_hours[21]; + // By default no restrictions + memset(usri2_logon_hours, 0xFF, _countof(usri2_logon_hours)); + if (!m_all) + { + int pos = 0; // in the array + int bit = 0; // bit index + const BYTE mask = 0b00000001; + for (int day = FIRST; day <= LAST; ++day) + { + for (int hour = 0; hour < 24; ++hour) + { + if (!m_data[day][hour]) + usri2_logon_hours[pos] &= ~(mask << bit); // clear bit + ++bit; + if (bit >= 8) + { + bit = 0; + ++pos; // next chunk + } + } + } + } + USER_INFO_1020 info; + DWORD parm_err; + info.usri1020_units_per_week = UNITS_PER_WEEK; // NetUserAdd and NetUserSetInfo functions ignore this member + info.usri1020_logon_hours = usri2_logon_hours; + if (NetUserSetInfo(NULL, username, 1020, reinterpret_cast(&info), &parm_err) != NERR_Success) + { + LOG_ERROR(__func__) << "NetUserSetInfo() failed with error " << GetLastError(); + return false; + } + return true; +} + +void LogonHours::Init(PBYTE usri2_logon_hours) +{ + m_all = true; + int day = Sun; + int hour = 0; // 0:00 + for (int p = 0; p < 21; ++p) + { + const BYTE chunk = usri2_logon_hours[p]; + BYTE mask = 0b00000001; + for (int bit = 0; bit < 8; ++bit) + { + m_data[day][hour] = (chunk & mask) != 0; + if (!m_data[day][hour]) + m_all = false; + ++hour; + mask <<= 1; + if (hour >= 24) + { + ++day; + hour = 0; + } + } + } +} + +bool LogonHours::InitFrom(const char* username) +{ + const std::wstring wusername(username, username + strlen(username)); + return InitFrom(wusername.c_str()); +} + +bool LogonHours::InitFrom(const wchar_t* username) +{ + LPBYTE bufptr; + // Refer to https://docs.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netusergetinfo + const NET_API_STATUS result = NetUserGetInfo(NULL, username, 2, &bufptr); + if (result != NERR_Success) + return false; + + const USER_INFO_2* const userinfo2 = reinterpret_cast(bufptr); + Init(userinfo2->usri2_logon_hours); + + NetApiBufferFree(bufptr); + + return true; +} + +bool LogonHours::Allowed(EDayOfWeek day, WORD hour) const +{ + if (Sun <= day && day <= Sat && 0 <= hour && hour <= 23) + return m_data[day][hour]; + return false; +} + +bool LogonHours::Allow(EDayOfWeek day, WORD hour, bool allow) +{ + if (Sun <= day && day <= Sat && 0 <= hour && hour <= 23) + if (m_data[day][hour] != allow) + { + m_data[day][hour] = allow; + if (!allow && m_all) + m_all = false; + return true; + } + return false; +} + +void LogonHours::Get(WeekHours& week, bool utc) const +{ + int pos = utc ? 0 : getSun0(); + for (int d = 0; d < 7; ++d) + { + for (WORD h = 0; h < 24; ++h) + { + week.array[pos] = m_data[d][h]; + ++pos; + if (pos >= sizeof(week.array)) + pos = 0; + } + } +} + +void LogonHours::Set(const WeekHours& week, bool utc) +{ + int pos = utc ? 0 : getSun0(); + for (int d = 0; d < 7; ++d) + { + for (WORD h = 0; h < 24; ++h) + { + m_data[d][h] = week.array[pos]; + ++pos; + if (pos >= sizeof(week.array)) + pos = 0; + } + } +} + +long LogonHours::SecondsLeft(EDayOfWeek day, WORD hour, WORD minute, WORD second) const +{ + if (All()) + return -1; + if (!Allowed(day, hour)) + return 0; + long left = (60 - second - 1) + 60 * (60 - minute - 1); // the rest of current hour is allowed + WORD hour_start = hour + 1; // for the current day - start with the next hour + EDayOfWeek d = day; + for (int i = 0; i < 7; ++i) // we'll check all week days starting from the current one + { + for (WORD h = hour_start; h < 24; ++h) // check hours until end of day + { + if (Allowed(day, h)) + left += 3600; + else + return left; + } + hour_start = 0; // for the rest of days + // next day + if (d == EDayOfWeek::LAST) + d = EDayOfWeek::FIRST; + else + ++d; + } + return left; +} + +int LogonHours::getSun0() +{ + TIME_ZONE_INFORMATION tz = { 0 }; + GetTimeZoneInformation(&tz); + return (tz.Bias < 0) ? (-tz.Bias / 60) : (7 * 24 - tz.Bias / 60); +} diff --git a/Common/LogonHours.h b/Common/LogonHours.h new file mode 100644 index 0000000..9c3c90a --- /dev/null +++ b/Common/LogonHours.h @@ -0,0 +1,50 @@ +#pragma once + +#include "WeekHours.h" + +enum EDayOfWeek +{ + // NOTE: the order can't be changed - it matches the logon hours bit-array + Sun, Mon, Tue, Wed, Thu, Fri, Sat, + FIRST = Sun, + LAST = Sat, +}; +EDayOfWeek& operator ++(EDayOfWeek& day); + +class LogonHours +{ +public: + LogonHours(); + LogonHours(PBYTE usri2_logon_hours); + + bool ApplyTo(const char* username) const; + bool ApplyTo(const wchar_t* username) const; + + void Init(PBYTE usri2_logon_hours); + + bool InitFrom(const char* username); + bool InitFrom(const wchar_t* username); + + bool All() const { return m_all; } + bool Allowed(EDayOfWeek day, WORD hour) const; + + bool Allow(EDayOfWeek day, WORD hour, bool allow); + + void Get(WeekHours& week, bool utc = true) const; + void Set(const WeekHours& week, bool utc = true); + + // @return seconds left (for the given time) until the allowed logon period expires, or -1 if no restrictions are set + // @note 0 (zero) is returned if logon isn't allowed during given time + long SecondsLeft(EDayOfWeek day, WORD hour, WORD minute, WORD second) const; + +protected: + /// + /// Calculates cell index which corresponds to Sun 0:00 UTC in local time zone. + /// + /// Zero-based index of cell in WeekHours.array + static int getSun0(); + +private: + bool m_all{ true }; // true if no logon restrictions + bool m_data[7][24]; +}; diff --git a/Common/REPEAT.h b/Common/REPEAT.h new file mode 100644 index 0000000..60cf78a --- /dev/null +++ b/Common/REPEAT.h @@ -0,0 +1,177 @@ +#pragma once + +// The approach was taken from https://dev.to/sgf4/repeat-macro-in-c-2hh0 + + +#define REPEAT_1(FN, START) FN(START+0) +#define REPEAT_2(FN, START) REPEAT_1(FN, START) FN(START+1) +#define REPEAT_3(FN, START) REPEAT_2(FN, START) FN(START+2) +#define REPEAT_4(FN, START) REPEAT_3(FN, START) FN(START+3) +#define REPEAT_5(FN, START) REPEAT_4(FN, START) FN(START+4) +#define REPEAT_6(FN, START) REPEAT_5(FN, START) FN(START+5) +#define REPEAT_7(FN, START) REPEAT_6(FN, START) FN(START+6) +#define REPEAT_8(FN, START) REPEAT_7(FN, START) FN(START+7) +#define REPEAT_9(FN, START) REPEAT_8(FN, START) FN(START+8) +#define REPEAT_10(FN, START) REPEAT_9(FN, START) FN(START+9) +#define REPEAT_11(FN, START) REPEAT_10(FN, START) FN(START+10) +#define REPEAT_12(FN, START) REPEAT_11(FN, START) FN(START+11) +#define REPEAT_13(FN, START) REPEAT_12(FN, START) FN(START+12) +#define REPEAT_14(FN, START) REPEAT_13(FN, START) FN(START+13) +#define REPEAT_15(FN, START) REPEAT_14(FN, START) FN(START+14) +#define REPEAT_16(FN, START) REPEAT_15(FN, START) FN(START+15) +#define REPEAT_17(FN, START) REPEAT_16(FN, START) FN(START+16) +#define REPEAT_18(FN, START) REPEAT_17(FN, START) FN(START+17) +#define REPEAT_19(FN, START) REPEAT_18(FN, START) FN(START+18) +#define REPEAT_20(FN, START) REPEAT_19(FN, START) FN(START+19) +#define REPEAT_21(FN, START) REPEAT_20(FN, START) FN(START+20) +#define REPEAT_22(FN, START) REPEAT_21(FN, START) FN(START+21) +#define REPEAT_23(FN, START) REPEAT_22(FN, START) FN(START+22) +#define REPEAT_24(FN, START) REPEAT_23(FN, START) FN(START+23) +#define REPEAT_25(FN, START) REPEAT_24(FN, START) FN(START+24) +#define REPEAT_26(FN, START) REPEAT_25(FN, START) FN(START+25) +#define REPEAT_27(FN, START) REPEAT_26(FN, START) FN(START+26) +#define REPEAT_28(FN, START) REPEAT_27(FN, START) FN(START+27) +#define REPEAT_29(FN, START) REPEAT_28(FN, START) FN(START+28) +#define REPEAT_30(FN, START) REPEAT_29(FN, START) FN(START+29) +#define REPEAT_31(FN, START) REPEAT_30(FN, START) FN(START+30) +#define REPEAT_32(FN, START) REPEAT_31(FN, START) FN(START+31) +#define REPEAT_33(FN, START) REPEAT_32(FN, START) FN(START+32) +#define REPEAT_34(FN, START) REPEAT_33(FN, START) FN(START+33) +#define REPEAT_35(FN, START) REPEAT_34(FN, START) FN(START+34) +#define REPEAT_36(FN, START) REPEAT_35(FN, START) FN(START+35) +#define REPEAT_37(FN, START) REPEAT_36(FN, START) FN(START+36) +#define REPEAT_38(FN, START) REPEAT_37(FN, START) FN(START+37) +#define REPEAT_39(FN, START) REPEAT_38(FN, START) FN(START+38) +#define REPEAT_40(FN, START) REPEAT_39(FN, START) FN(START+39) +#define REPEAT_41(FN, START) REPEAT_40(FN, START) FN(START+40) +#define REPEAT_42(FN, START) REPEAT_41(FN, START) FN(START+41) +#define REPEAT_43(FN, START) REPEAT_42(FN, START) FN(START+42) +#define REPEAT_44(FN, START) REPEAT_43(FN, START) FN(START+43) +#define REPEAT_45(FN, START) REPEAT_44(FN, START) FN(START+44) +#define REPEAT_46(FN, START) REPEAT_45(FN, START) FN(START+45) +#define REPEAT_47(FN, START) REPEAT_46(FN, START) FN(START+46) +#define REPEAT_48(FN, START) REPEAT_47(FN, START) FN(START+47) +#define REPEAT_49(FN, START) REPEAT_48(FN, START) FN(START+48) +#define REPEAT_50(FN, START) REPEAT_49(FN, START) FN(START+49) +#define REPEAT_51(FN, START) REPEAT_50(FN, START) FN(START+50) +#define REPEAT_52(FN, START) REPEAT_51(FN, START) FN(START+51) +#define REPEAT_53(FN, START) REPEAT_52(FN, START) FN(START+52) +#define REPEAT_54(FN, START) REPEAT_53(FN, START) FN(START+53) +#define REPEAT_55(FN, START) REPEAT_54(FN, START) FN(START+54) +#define REPEAT_56(FN, START) REPEAT_55(FN, START) FN(START+55) +#define REPEAT_57(FN, START) REPEAT_56(FN, START) FN(START+56) +#define REPEAT_58(FN, START) REPEAT_57(FN, START) FN(START+57) +#define REPEAT_59(FN, START) REPEAT_58(FN, START) FN(START+58) +#define REPEAT_60(FN, START) REPEAT_59(FN, START) FN(START+59) +#define REPEAT_61(FN, START) REPEAT_60(FN, START) FN(START+60) +#define REPEAT_62(FN, START) REPEAT_61(FN, START) FN(START+61) +#define REPEAT_63(FN, START) REPEAT_62(FN, START) FN(START+62) +#define REPEAT_64(FN, START) REPEAT_63(FN, START) FN(START+63) +#define REPEAT_65(FN, START) REPEAT_64(FN, START) FN(START+64) +#define REPEAT_66(FN, START) REPEAT_65(FN, START) FN(START+65) +#define REPEAT_67(FN, START) REPEAT_66(FN, START) FN(START+66) +#define REPEAT_68(FN, START) REPEAT_67(FN, START) FN(START+67) +#define REPEAT_69(FN, START) REPEAT_68(FN, START) FN(START+68) +#define REPEAT_70(FN, START) REPEAT_69(FN, START) FN(START+69) +#define REPEAT_71(FN, START) REPEAT_70(FN, START) FN(START+70) +#define REPEAT_72(FN, START) REPEAT_71(FN, START) FN(START+71) +#define REPEAT_73(FN, START) REPEAT_72(FN, START) FN(START+72) +#define REPEAT_74(FN, START) REPEAT_73(FN, START) FN(START+73) +#define REPEAT_75(FN, START) REPEAT_74(FN, START) FN(START+74) +#define REPEAT_76(FN, START) REPEAT_75(FN, START) FN(START+75) +#define REPEAT_77(FN, START) REPEAT_76(FN, START) FN(START+76) +#define REPEAT_78(FN, START) REPEAT_77(FN, START) FN(START+77) +#define REPEAT_79(FN, START) REPEAT_78(FN, START) FN(START+78) +#define REPEAT_80(FN, START) REPEAT_79(FN, START) FN(START+79) +#define REPEAT_81(FN, START) REPEAT_80(FN, START) FN(START+80) +#define REPEAT_82(FN, START) REPEAT_81(FN, START) FN(START+81) +#define REPEAT_83(FN, START) REPEAT_82(FN, START) FN(START+82) +#define REPEAT_84(FN, START) REPEAT_83(FN, START) FN(START+83) +#define REPEAT_85(FN, START) REPEAT_84(FN, START) FN(START+84) +#define REPEAT_86(FN, START) REPEAT_85(FN, START) FN(START+85) +#define REPEAT_87(FN, START) REPEAT_86(FN, START) FN(START+86) +#define REPEAT_88(FN, START) REPEAT_87(FN, START) FN(START+87) +#define REPEAT_89(FN, START) REPEAT_88(FN, START) FN(START+88) +#define REPEAT_90(FN, START) REPEAT_89(FN, START) FN(START+89) +#define REPEAT_91(FN, START) REPEAT_90(FN, START) FN(START+90) +#define REPEAT_92(FN, START) REPEAT_91(FN, START) FN(START+91) +#define REPEAT_93(FN, START) REPEAT_92(FN, START) FN(START+92) +#define REPEAT_94(FN, START) REPEAT_93(FN, START) FN(START+93) +#define REPEAT_95(FN, START) REPEAT_94(FN, START) FN(START+94) +#define REPEAT_96(FN, START) REPEAT_95(FN, START) FN(START+95) +#define REPEAT_97(FN, START) REPEAT_96(FN, START) FN(START+96) +#define REPEAT_98(FN, START) REPEAT_97(FN, START) FN(START+97) +#define REPEAT_99(FN, START) REPEAT_98(FN, START) FN(START+98) +#define REPEAT_100(FN, START) REPEAT_99(FN, START) FN(START+99) +#define REPEAT_101(FN, START) REPEAT_100(FN, START) FN(START+100) +#define REPEAT_102(FN, START) REPEAT_101(FN, START) FN(START+101) +#define REPEAT_103(FN, START) REPEAT_102(FN, START) FN(START+102) +#define REPEAT_104(FN, START) REPEAT_103(FN, START) FN(START+103) +#define REPEAT_105(FN, START) REPEAT_104(FN, START) FN(START+104) +#define REPEAT_106(FN, START) REPEAT_105(FN, START) FN(START+105) +#define REPEAT_107(FN, START) REPEAT_106(FN, START) FN(START+106) +#define REPEAT_108(FN, START) REPEAT_107(FN, START) FN(START+107) +#define REPEAT_109(FN, START) REPEAT_108(FN, START) FN(START+108) +#define REPEAT_110(FN, START) REPEAT_109(FN, START) FN(START+109) +#define REPEAT_111(FN, START) REPEAT_110(FN, START) FN(START+110) +#define REPEAT_112(FN, START) REPEAT_111(FN, START) FN(START+111) +#define REPEAT_113(FN, START) REPEAT_112(FN, START) FN(START+112) +#define REPEAT_114(FN, START) REPEAT_113(FN, START) FN(START+113) +#define REPEAT_115(FN, START) REPEAT_114(FN, START) FN(START+114) +#define REPEAT_116(FN, START) REPEAT_115(FN, START) FN(START+115) +#define REPEAT_117(FN, START) REPEAT_116(FN, START) FN(START+116) +#define REPEAT_118(FN, START) REPEAT_117(FN, START) FN(START+117) +#define REPEAT_119(FN, START) REPEAT_118(FN, START) FN(START+118) +#define REPEAT_120(FN, START) REPEAT_119(FN, START) FN(START+119) +#define REPEAT_121(FN, START) REPEAT_120(FN, START) FN(START+120) +#define REPEAT_122(FN, START) REPEAT_121(FN, START) FN(START+121) +#define REPEAT_123(FN, START) REPEAT_122(FN, START) FN(START+122) +#define REPEAT_124(FN, START) REPEAT_123(FN, START) FN(START+123) +#define REPEAT_125(FN, START) REPEAT_124(FN, START) FN(START+124) +#define REPEAT_126(FN, START) REPEAT_125(FN, START) FN(START+125) +#define REPEAT_127(FN, START) REPEAT_126(FN, START) FN(START+126) +#define REPEAT_128(FN, START) REPEAT_127(FN, START) FN(START+127) +#define REPEAT_129(FN, START) REPEAT_128(FN, START) FN(START+128) +#define REPEAT_130(FN, START) REPEAT_129(FN, START) FN(START+129) +#define REPEAT_131(FN, START) REPEAT_130(FN, START) FN(START+130) +#define REPEAT_132(FN, START) REPEAT_131(FN, START) FN(START+131) +#define REPEAT_133(FN, START) REPEAT_132(FN, START) FN(START+132) +#define REPEAT_134(FN, START) REPEAT_133(FN, START) FN(START+133) +#define REPEAT_135(FN, START) REPEAT_134(FN, START) FN(START+134) +#define REPEAT_136(FN, START) REPEAT_135(FN, START) FN(START+135) +#define REPEAT_137(FN, START) REPEAT_136(FN, START) FN(START+136) +#define REPEAT_138(FN, START) REPEAT_137(FN, START) FN(START+137) +#define REPEAT_139(FN, START) REPEAT_138(FN, START) FN(START+138) +#define REPEAT_140(FN, START) REPEAT_139(FN, START) FN(START+139) +#define REPEAT_141(FN, START) REPEAT_140(FN, START) FN(START+140) +#define REPEAT_142(FN, START) REPEAT_141(FN, START) FN(START+141) +#define REPEAT_143(FN, START) REPEAT_142(FN, START) FN(START+142) +#define REPEAT_144(FN, START) REPEAT_143(FN, START) FN(START+143) +#define REPEAT_145(FN, START) REPEAT_144(FN, START) FN(START+144) +#define REPEAT_146(FN, START) REPEAT_145(FN, START) FN(START+145) +#define REPEAT_147(FN, START) REPEAT_146(FN, START) FN(START+146) +#define REPEAT_148(FN, START) REPEAT_147(FN, START) FN(START+147) +#define REPEAT_149(FN, START) REPEAT_148(FN, START) FN(START+148) +#define REPEAT_150(FN, START) REPEAT_149(FN, START) FN(START+149) +#define REPEAT_151(FN, START) REPEAT_150(FN, START) FN(START+150) +#define REPEAT_152(FN, START) REPEAT_151(FN, START) FN(START+151) +#define REPEAT_153(FN, START) REPEAT_152(FN, START) FN(START+152) +#define REPEAT_154(FN, START) REPEAT_153(FN, START) FN(START+153) +#define REPEAT_155(FN, START) REPEAT_154(FN, START) FN(START+154) +#define REPEAT_156(FN, START) REPEAT_155(FN, START) FN(START+155) +#define REPEAT_157(FN, START) REPEAT_156(FN, START) FN(START+156) +#define REPEAT_158(FN, START) REPEAT_157(FN, START) FN(START+157) +#define REPEAT_159(FN, START) REPEAT_158(FN, START) FN(START+158) +#define REPEAT_160(FN, START) REPEAT_159(FN, START) FN(START+159) +#define REPEAT_161(FN, START) REPEAT_160(FN, START) FN(START+160) +#define REPEAT_162(FN, START) REPEAT_161(FN, START) FN(START+161) +#define REPEAT_163(FN, START) REPEAT_162(FN, START) FN(START+162) +#define REPEAT_164(FN, START) REPEAT_163(FN, START) FN(START+163) +#define REPEAT_165(FN, START) REPEAT_164(FN, START) FN(START+164) +#define REPEAT_166(FN, START) REPEAT_165(FN, START) FN(START+165) +#define REPEAT_167(FN, START) REPEAT_166(FN, START) FN(START+166) +#define REPEAT_168(FN, START) REPEAT_167(FN, START) FN(START+167) + +#define REPEAT_N(FN, N, START) REPEAT_##N(FN, START) +// #define EVAL(x) #x +// TODO? #define REPEAT(FN, N1, N2) REPEAT_N(FN, EVAL(N2-N1), N1) diff --git a/Common/WeekHours.h b/Common/WeekHours.h new file mode 100644 index 0000000..81a6f83 --- /dev/null +++ b/Common/WeekHours.h @@ -0,0 +1,8 @@ +#pragma once + +union WeekHours +{ + bool array [7 * 24]; + bool table [7] [24]; +}; + diff --git a/LogonHoursService/version.h b/Common/version.h similarity index 62% rename from LogonHoursService/version.h rename to Common/version.h index 2d6c3a5..cffd4bd 100644 --- a/LogonHoursService/version.h +++ b/Common/version.h @@ -8,9 +8,9 @@ #endif #define VERSION_MAJOR 1 -#define VERSION_MINOR 0 -#define VERSION_REV 3 +#define VERSION_MINOR 1 +#define VERSION_REV 0 -#define VERSION_STRING "1.0.3 Alpha" VERSION_CONFIG +#define VERSION_STRING "1.1.0 Alpha" VERSION_CONFIG #endif diff --git a/LogonHoursManager/LogonHoursManager.vcxproj b/LogonHoursManager/LogonHoursManager.vcxproj new file mode 100644 index 0000000..0473486 --- /dev/null +++ b/LogonHoursManager/LogonHoursManager.vcxproj @@ -0,0 +1,154 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {1fb09ad5-5bea-427a-9862-6884a60aa97a} + LogonHoursManager + 10.0 + + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + Use + + + Windows + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + Use + + + Windows + true + true + true + + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + Use + + + Windows + true + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + Use + + + Windows + true + true + true + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/LogonHoursManager/LogonHoursManager.vcxproj.filters b/LogonHoursManager/LogonHoursManager.vcxproj.filters new file mode 100644 index 0000000..1bb7466 --- /dev/null +++ b/LogonHoursManager/LogonHoursManager.vcxproj.filters @@ -0,0 +1,70 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + + + Resource Files + + + + + + + + + \ No newline at end of file diff --git a/LogonHoursManager/MainDialog.cpp b/LogonHoursManager/MainDialog.cpp new file mode 100644 index 0000000..fb8106a --- /dev/null +++ b/LogonHoursManager/MainDialog.cpp @@ -0,0 +1,260 @@ +#include "stdafx.h" +#include "MainDialog.h" + +#include "MainModel.h" + +CMainDialog::CMainDialog(MainModel& model) + :m_model(model) +{ +} + +CMainDialog::~CMainDialog() +{ +} + +LRESULT CMainDialog::OnClose(UINT, int, HWND) +{ + if (m_week_modified) + { + TCHAR message[1024]; + _stprintf_s(message, _T("Allowed Logon Hours for the user '%s' were modified.\nDo you want to apply the changes?"), m_userName); + const auto answer = MessageBox(message, _T("Confirm the changes"), MB_ICONQUESTION | MB_YESNOCANCEL); + switch (answer) + { + case IDCANCEL: + return 0; + case IDYES: + ApplyChanges(true); + break; + case IDNO: + // discard changes + break; + } + } + EndDialog(0); + return 0; +} + +void CMainDialog::OnDataExchangeError(UINT, BOOL) +{ +} + +LRESULT CMainDialog::OnInitDialog(HWND, LPARAM) +{ + HICON hIcon = ::LoadIcon(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDI_APP)); + SetIcon(hIcon); + + if (!m_model.isElevated()) + { + SetDlgItemText(IDOK, _T("Restart")); + SendDlgItemMessage(IDOK, BCM_SETSHIELD, 0, TRUE); + } + + m_ctrlToolTip.Create(m_hWnd); // , rcDefault, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, WS_EX_TOPMOST); + ATLASSERT(m_ctrlToolTip.IsWindow()); + + const auto& wndGroup = GetDlgItem(IDC_GROUP_HOURS); + RECT rcGroup; + wndGroup.GetWindowRect(&rcGroup); + // MapDialogRect(&rcGroup); + + const auto units = GetDialogBaseUnits(); + const auto xunits = LOWORD(units); + const auto yunits = HIWORD(units); + + UINT xshift = 30; // width of left-most column with day names + const UINT yshift = 4; + UINT width = (rcGroup.right - rcGroup.left - xshift - 10) / 24; // width of each column with a checkbox + UINT height = (rcGroup.bottom - rcGroup.top - 12 - yshift) / 8; // height of each row (applies to header as well) + // starting ID for checkboxes: Sun 0:00-1:00, Sun 1:00-2:00, ..., Sun 23:00-24:00, Mon 0:00-1:00, etc. + UINT id = IDC_HOURS_FIRST; + // Columns labels + for (WORD hour = 0; hour < 24; ++hour) + { + UINT x = rcGroup.left + xshift + hour * width; + UINT y = rcGroup.top + yshift - height * 3 / 4; + //if (hour % 2 == 0) + { + wchar_t header[12]; + swprintf_s(header, L"%02d", hour); + const auto hwndLabel = CreateWindow(_T("Static"), header, WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_LEFT, + x, y, width, height, m_hWnd, HMENU(IDC_STATIC), NULL, NULL); + SendMessage(hwndLabel, WM_SETFONT, WPARAM(GetFont()), 0); + } + } + static const wchar_t* namesShort[] + = { L"SUN", L"MON", L"TUE", L"WED", L"THU", L"FRI", L"SAT" }; + static const wchar_t* namesLong[] + = { L"Sunday", L"Monday", L"Tuesday", L"Wednesday", L"Thursday", L"Friday", L"Saturday" }; + // Row labels and check boxes + for (WORD day = 0; day < 7; ++day) + { + const UINT x = rcGroup.left; + const UINT y = rcGroup.top + yshift + height * day + height / 5; + + const auto hwndLabel = CreateWindow(_T("Static"), namesShort[day], WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_LEFT, + x, y, xshift, height, m_hWnd, HMENU(IDC_STATIC), NULL, NULL); + SendMessage(hwndLabel, WM_SETFONT, WPARAM(GetFont()), 0); + + // TODO? CToolInfo ti(TTF_SUBCLASS, hwndLabel, 0, NULL, LPTSTR(namesLong[day])); + // TODO? ATLASSERT(m_ctrlToolTip.AddTool(ti)); + + for (WORD hour = 0; hour < 24; ++hour) + { + const UINT x = rcGroup.left + xshift + hour * width; + const auto hwndCheckBox = CreateWindow(_T("Button"), NULL, WS_CHILD | WS_VISIBLE | WS_EX_NOPARENTNOTIFY | BS_CHECKBOX | BS_PUSHLIKE, + x, y, width, height, m_hWnd, HMENU(id), NULL, NULL); +#if 1 + wchar_t tttext[42]; + swprintf_s(tttext, L"%s %02d:00-%02d:00", namesLong[day], hour, hour + 1); + CToolInfo ti(TTF_SUBCLASS, hwndCheckBox, 0, NULL, tttext); + ATLASSERT(m_ctrlToolTip.AddTool(ti)); + // m_ctrlToolTip.AddTool(hwndCheckBox, tttext); // , & rcTool, TTF_IDISHWND); +#else + RECT rcTool; + rcTool.left = x; + rcTool.top = y; + rcTool.right = x + width; + rcTool.bottom = y + height; + TOOLINFOW toolinfo = { 0 }; + toolinfo.cbSize = sizeof(toolinfo); // TTTOOLINFO_V1_SIZE; + toolinfo.hwnd = m_hWnd; + toolinfo.rect = rcTool; + // toolinfo.uFlags = TTF_IDISHWND; + toolinfo.uId = id; + toolinfo.lpszText = LPTSTR(L"Ýòî ïîëå äëÿ ââîäà òåêñòà"); + ::SendMessageW(m_ctrlToolTip, TTM_ADDTOOL, 0, LPARAM(&toolinfo)); +#endif + + ++id; + } + } + + m_ctrlToolTip.Activate(TRUE); + + FetchData(); + + UIAddChildWindowContainer(m_hWnd); + UpdateUI(true); + + return 0; +} + +LRESULT CMainDialog::OnOK(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& bHandled) +{ + if (m_model.isElevated()) + { + ApplyChanges(false); + } + else + { + // TODO move to another class? + + TCHAR filepath[MAX_PATH]; + GetModuleFileName(NULL, filepath, sizeof(filepath)); + + TCHAR curdir[MAX_PATH]; + GetCurrentDirectory(_countof(curdir), curdir); + + SHELLEXECUTEINFO shex = { 0 }; + + shex.cbSize = sizeof(shex); + shex.fMask = SEE_MASK_UNICODE | SEE_MASK_NOZONECHECKS | SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC; + shex.lpVerb = L"runas"; + shex.nShow = SW_SHOW; + shex.lpFile = filepath; + // TODO pass currently selected user + shex.lpParameters = GetCommandLine(); + shex.lpDirectory = curdir; + + if (ShellExecuteEx(&shex)) + { + EndDialog(0); + } + else + LOG_ERROR(__func__) << "ShellExecuteEx() failed with error " << GetLastError(); + } + // bHandled = TRUE; + return 0; +} + +LRESULT CMainDialog::OnHourChanged(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) +{ + if (wNotifyCode == BN_CLICKED) + { + if (m_model.isElevated()) + { + m_week_modified = true; + UpdateUI(); + } + else + { + bHandled = FALSE; + + } + } + return 0; +} + +LRESULT CMainDialog::OnUserChanged(UINT, int, HWND) +{ + if (m_week_modified) + { + TCHAR message[1024]; + _stprintf_s(message, _T("Allowed Logon Hours for the user '%s' were modified.\nDo you want to apply the changes?"), m_userName); + if (MessageBox(message, _T("Confirm the changes"), MB_ICONQUESTION | MB_YESNO) == IDYES) + { + ApplyChanges(true); + } + } + UpdateUI(); + m_model.selectUser(m_userName); + FetchData(); + UpdateUI(true); + return 0; +} + +void CMainDialog::ApplyChanges(bool prev_user) +{ + const std::wstring userName = prev_user ? m_userName : L""; // m_userName will be changed during DDX + DoDataExchange(DDX_SAVE); + m_model.getHours().Set(m_week, false); + if (m_model.getHours().ApplyTo(userName.empty() ? m_userName : userName.c_str())) + { + m_week_modified = false; + } + else + { + MessageBox(_T("Cannot apply these changes!"), _T("Error"), MB_ICONERROR | MB_OK); + } + UpdateUI(false); +} + +void CMainDialog::FetchData() +{ + wcscpy_s(m_userName, m_model.getUser()); + m_users = m_model.getUsers(); + m_model.getHours().Get(m_week, false); + m_week_modified = false; + DoDataExchange(DDX_LOAD); +} + +void CMainDialog::UpdateUI(bool saved) +{ + const auto isValid = saved || DoDataExchange(DDX_SAVE); + + for (UINT id = IDC_HOURS_FIRST; id <= IDC_HOURS_LAST; ++id) + UIEnable(id, isValid && m_userName[0] != 0 && m_model.isElevated()); + + if (isValid) + { + UIEnable(IDOK, !m_model.isElevated() || m_week_modified); + } + else + { + UIEnable(IDOK, FALSE); + } + + UIUpdateChildWindows(); +} + diff --git a/LogonHoursManager/MainDialog.h b/LogonHoursManager/MainDialog.h new file mode 100644 index 0000000..d77aee3 --- /dev/null +++ b/LogonHoursManager/MainDialog.h @@ -0,0 +1,96 @@ +#pragma once +#include // MSG_WM_INITDIALOG, etc. +#include // CToolTipCtrl +#include // CUpdateUI +#include + +#include + +#include + +#include "WinDataExchangeEx.h" +#include "UIEx.h" + +#include "resource.h" + +class MainModel; + +class CMainDialog + : public CDialogImpl + , public CUpdateUI + , public CWinDataExchangeEx +{ + typedef CMainDialog Self; + +private: + MainModel& m_model; + + CToolTipCtrl m_ctrlToolTip; + + CComboBox m_comboUserName; + TCHAR m_userName[256] = _T(""); + std::vector m_users; + + /// + /// Temporary data while the user is editing it + /// + WeekHours m_week; + bool m_week_modified{ false }; + +public: + enum EID + { + IDD = IDD_MAIN, + + IDC_HOURS_FIRST = IDC_GROUP_HOURS + 1, + IDC_HOURS_LAST = IDC_HOURS_FIRST + 7 * 24 - 1, + IDC_HOURS_COUNT = IDC_HOURS_LAST - IDC_HOURS_FIRST, + }; + + BEGIN_MSG_MAP(Self) + MSG_WM_INITDIALOG(OnInitDialog) + COMMAND_ID_HANDLER(IDOK, OnOK) + COMMAND_RANGE_HANDLER(IDC_HOURS_FIRST, IDC_HOURS_LAST, OnHourChanged) + COMMAND_HANDLER_EX(IDC_COMBO_USER, CBN_SELENDOK, OnUserChanged) + COMMAND_HANDLER_EX(IDCANCEL, BN_CLICKED, OnClose) + END_MSG_MAP() + + BEGIN_DDX_MAP(Self) + DDX_CONTROL_HANDLE(IDC_COMBO_USER, m_comboUserName) + DDX_COMBO_LIST_SOURCE(IDC_COMBO_USER, m_users) + DDX_COMBO_TEXT(IDC_COMBO_USER, m_userName) + DDX_CHECK_ARRAY(IDC_HOURS_FIRST, IDC_HOURS_LAST, m_week.array) + END_DDX_MAP() + + BEGIN_UPDATE_UI_MAP(Self) + UPDATE_ELEMENT(IDC_COMBO_USER, UPDUI_CHILDWINDOW) + UPDATE_ELEMENT(IDOK, UPDUI_CHILDWINDOW) + UPDATE_CHILDWINDOWS(IDC_HOURS_FIRST, IDC_HOURS_LAST, 168) // 7 * 24 + // TODO? UPDATE_ELEMENT(IDC_HOURS_FIRST, UPDUI_CHILDWINDOW) + // TODO? UPDATE_ELEMENT(IDC_HOURS_LAST, UPDUI_CHILDWINDOW) + END_UPDATE_UI_MAP() + + CMainDialog(MainModel& model); + ~CMainDialog(); + +public: + LRESULT OnClose(UINT, int, HWND); + + void OnDataExchangeError(UINT, BOOL); + + LRESULT OnInitDialog(HWND, LPARAM); + +private: + LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/); + + LRESULT OnHourChanged(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled); + + LRESULT OnUserChanged(UINT, int, HWND); + +private: + void ApplyChanges(bool prev_user); + + void FetchData(); + + void UpdateUI(bool saved = false); +}; diff --git a/LogonHoursManager/MainModel.cpp b/LogonHoursManager/MainModel.cpp new file mode 100644 index 0000000..3c13f59 --- /dev/null +++ b/LogonHoursManager/MainModel.cpp @@ -0,0 +1,111 @@ +#include "stdafx.h" +#include "MainModel.h" + +struct MainModel::Data +{ + // username => logon hours + std::map hours; + + std::wstring user; // active user +}; + +MainModel::MainModel() + : m_(new Data) +{ + fetchUsers(); + selectUser(nullptr); +} + +MainModel::~MainModel() +{ + delete m_; +} + +const LogonHours& MainModel::getHours() const +{ + return m_->hours[m_->user]; +} + +LogonHours& MainModel::getHours() +{ + return m_->hours[m_->user]; +} + +const wchar_t* MainModel::getUser() const +{ + return m_->user.c_str(); +} + +std::vector MainModel::getUsers() const +{ + std::vector users; + for (const auto& it : m_->hours) + { + users.push_back(it.first); + } + return users; +} + +bool MainModel::isElevated() const +{ + static bool init_ok{ false }; + static TOKEN_ELEVATION_TYPE type; + if (!init_ok) + { + init_ok = true; + HANDLE hProcess; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hProcess)) + { + DWORD size; + if (GetTokenInformation(hProcess, TokenElevationType, &type, sizeof(type), &size)) + { + if (size == sizeof(type)) + { + LOG_DEBUG(__func__) << "GetTokenInformation: TOKEN_ELEVATION_TYPE = " << int(type); + } + } + else + LOG_ERROR(__func__) << "GetTokenInformation(..., TokenElevationType, ...) failed with error " << GetLastError(); + CloseHandle(hProcess); + } + else + LOG_ERROR(__func__) << "OpenProcessToken(GetCurrentProcess(), TOKEN_READ, ...) failed with error " << GetLastError(); + } + + return type == TokenElevationTypeFull; +} + +void MainModel::selectUser(const wchar_t* user) +{ + if (user) + { + m_->user = user; + } + else + { + TCHAR username[256]; + DWORD len = _countof(username); + if (GetUserName(username, &len) == 0) + LOG_ERROR(__func__) << "GetUserName() failed with error " << GetLastError(); + else + m_->user = username; + } + + if (!m_->user.empty()) + m_->hours[m_->user].InitFrom(m_->user.c_str()); +} + +void MainModel::fetchUsers() +{ + LPBYTE buffer; + DWORD entriesread, totalentries; + if (NetUserEnum(NULL, 1, FILTER_NORMAL_ACCOUNT, &buffer, MAX_PREFERRED_LENGTH, &entriesread, &totalentries, NULL) != NERR_Success) + return; + const auto* const userinfo = reinterpret_cast(buffer); + for (DWORD i = 0; i < entriesread; ++i) + { + if ((userinfo[i].usri1_flags & USER_PRIV_MASK) == USER_PRIV_USER) + m_->hours[userinfo[i].usri1_name] = LogonHours(); + } + NetApiBufferFree(buffer); +} diff --git a/LogonHoursManager/MainModel.h b/LogonHoursManager/MainModel.h new file mode 100644 index 0000000..7bb4a78 --- /dev/null +++ b/LogonHoursManager/MainModel.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +class MainModel +{ +public: + MainModel(); + ~MainModel(); + + const LogonHours& getHours() const; + LogonHours& getHours(); + + const wchar_t* getUser() const; + std::vector getUsers() const; + + bool isElevated() const; + + void selectUser(const wchar_t* user); + +protected: + void fetchUsers(); + +private: + struct Data; + Data* const m_; +}; diff --git a/LogonHoursManager/UIEx.h b/LogonHoursManager/UIEx.h new file mode 100644 index 0000000..1ca4a70 --- /dev/null +++ b/LogonHoursManager/UIEx.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#define UPDATE_CHILDWINDOW(nID) \ + { nID, UPDUI_CHILDWINDOW }, + +#define UPDATE_CHILDWINDOWS(nStartID, nEndId, count) \ + REPEAT_N(UPDATE_CHILDWINDOW, count, nStartID) diff --git a/LogonHoursManager/WinDataExchangeEx.h b/LogonHoursManager/WinDataExchangeEx.h new file mode 100644 index 0000000..9c714bd --- /dev/null +++ b/LogonHoursManager/WinDataExchangeEx.h @@ -0,0 +1,121 @@ +#pragma once + +#include +#include + +template +class CWinDataExchangeEx : public CWinDataExchange +{ + typedef CWinDataExchange Base_; + +public: + BOOL DDX_Combo_Text(UINT nID, LPTSTR lpstrText, int cbSize, BOOL bSave, BOOL bValidate = FALSE, int nLength = 0) + { + T* const pT = static_cast(this); + BOOL bSuccess = TRUE; + + HWND hWndCtrl = pT->GetDlgItem(nID); + if (bSave) + { + int nRetLen = ::GetWindowText(hWndCtrl, lpstrText, cbSize / sizeof(TCHAR)); + if (nRetLen < ::GetWindowTextLength(hWndCtrl)) + bSuccess = FALSE; + } + else + { + ATLASSERT(!bValidate || (lstrlen(lpstrText) <= nLength)); + // bSuccess = pT->SetDlgItemText(nID, lpstrText); // doesn't work for combo boxes and similar controls + // SetWindowText() won't help either + bSuccess = ComboBox_SelectString(hWndCtrl, -1, lpstrText) != CB_ERR; + } + + if (!bSuccess) + { + pT->OnDataExchangeError(nID, bSave); + } + else if (bSave && bValidate) // validation + { + ATLASSERT(nLength > 0); + if (lstrlen(lpstrText) > nLength) + { + typename Base_::_XData data = { Base_::ddxDataText }; + data.textData.nLength = lstrlen(lpstrText); + data.textData.nMaxLength = nLength; + pT->OnDataValidateError(nID, bSave, data); + bSuccess = FALSE; + } + } + return bSuccess; + } + + template + BOOL DDX_Combo_List(UINT nID, Container& list, BOOL bSave, BOOL bValidate = FALSE, int nLength = 0) + { + T* const pT = static_cast(this); + BOOL bSuccess = TRUE; + + HWND hWndCtrl = pT->GetDlgItem(nID); + if (bSave) + { + const int count = ComboBox_GetCount(hWndCtrl); + list.clear(); + for (int i = 0; i < count; ++i) + { + TCHAR str[256] = { 0 }; + ComboBox_GetLBText(hWndCtrl, i, str); + list.push_back(str); + } + } + else + { + ComboBox_ResetContent(hWndCtrl); + for (const auto& str : list) + { + ComboBox_AddString(hWndCtrl, str.c_str()); + } + } + + if (!bSuccess) + { + pT->OnDataExchangeError(nID, bSave); + } + else if (bSave && bValidate) // validation + { +/*TODO? + ATLASSERT(nLength > 0); + if (strText.GetLength() > nLength) + { + _XData data = { ddxDataText }; + data.textData.nLength = strText.GetLength(); + data.textData.nMaxLength = nLength; + pT->OnDataValidateError(nID, bSave, data); + bSuccess = FALSE; + } +*/ + } + return bSuccess; + } +}; + +#define DDX_CHECK_ARRAY(nFirstID, nLastID, arr) \ + static_assert(_countof(arr) == nLastID - nFirstID + 1, "The elements range should match the array's size"); \ + if (nFirstID <= nCtlID && nCtlID <= nLastID) \ + DDX_Check(nCtlID, arr[nCtlID - nFirstID], bSaveAndValidate); \ + if (nCtlID == (UINT)-1) \ + for (UINT id = nFirstID; id <= nLastID; ++id) \ + DDX_Check(id, arr[id - nFirstID], bSaveAndValidate); + +#define DDX_COMBO_LIST_SOURCE(nID, var) \ + if((nCtlID == (UINT)-1) || (nCtlID == nID)) \ + { \ + if (!bSaveAndValidate) \ + if(!DDX_Combo_List(nID, var, bSaveAndValidate)) \ + return FALSE; \ + } + +#define DDX_COMBO_TEXT(nID, var) \ + if((nCtlID == (UINT)-1) || (nCtlID == nID)) \ + { \ + if(!DDX_Combo_Text(nID, var, sizeof(var), bSaveAndValidate)) \ + return FALSE; \ + } diff --git a/LogonHoursManager/WinMain.cpp b/LogonHoursManager/WinMain.cpp new file mode 100644 index 0000000..16d4c31 --- /dev/null +++ b/LogonHoursManager/WinMain.cpp @@ -0,0 +1,204 @@ +#include "stdafx.h" + +#include + +#include "MainDialog.h" +#include "MainModel.h" + +#if 1 +CAppModule _Module; + +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance + , _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) +{ + LogMain(); + + _Module.Init(NULL, hInstance); + + MainModel model; + + CMainDialog dlg(model); + dlg.DoModal(); + + _Module.Term(); + + return 0; +} +#else + +#define MAX_LOADSTRING 100 + +// Global Variables: +HINSTANCE hInst; // current instance +WCHAR szTitle[MAX_LOADSTRING]; // The title bar text +WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name + +// Forward declarations of functions included in this code module: +ATOM MyRegisterClass(HINSTANCE hInstance); +BOOL InitInstance(HINSTANCE, int); +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); + +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ int nCmdShow) +{ + UNREFERENCED_PARAMETER(hPrevInstance); + UNREFERENCED_PARAMETER(lpCmdLine); + + // TODO: Place code here. + + // Initialize global strings + LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); + LoadStringW(hInstance, IDS_WINDOW_CLASS, szWindowClass, MAX_LOADSTRING); + MyRegisterClass(hInstance); + + // Perform application initialization: + if (!InitInstance (hInstance, nCmdShow)) + { + return FALSE; + } + + HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_ACCELERATORS)); + + MSG msg; + + // Main message loop: + while (GetMessage(&msg, nullptr, 0, 0)) + { + if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + return (int) msg.wParam; +} + + + +// +// FUNCTION: MyRegisterClass() +// +// PURPOSE: Registers the window class. +// +ATOM MyRegisterClass(HINSTANCE hInstance) +{ + WNDCLASSEXW wcex; + + wcex.cbSize = sizeof(WNDCLASSEX); + + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APP)); + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wcex.lpszMenuName = MAKEINTRESOURCEW(IDM_APP); + wcex.lpszClassName = szWindowClass; + wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APP)); + + return RegisterClassExW(&wcex); +} + +// +// FUNCTION: InitInstance(HINSTANCE, int) +// +// PURPOSE: Saves instance handle and creates main window +// +// COMMENTS: +// +// In this function, we save the instance handle in a global variable and +// create and display the main program window. +// +BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) +{ + hInst = hInstance; // Store instance handle in our global variable + + HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); + + if (!hWnd) + { + return FALSE; + } + + ShowWindow(hWnd, nCmdShow); + UpdateWindow(hWnd); + + return TRUE; +} + +// +// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) +// +// PURPOSE: Processes messages for the main window. +// +// WM_COMMAND - process the application menu +// WM_PAINT - Paint the main window +// WM_DESTROY - post a quit message and return +// +// +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_COMMAND: + { + int wmId = LOWORD(wParam); + // Parse the menu selections: + switch (wmId) + { + case IDM_ABOUT: + DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); + break; + case IDM_EXIT: + DestroyWindow(hWnd); + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + } + break; + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hWnd, &ps); + // TODO: Add any drawing code that uses hdc here... + EndPaint(hWnd, &ps); + } + break; + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + +// Message handler for about box. +INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + return (INT_PTR)TRUE; + + case WM_COMMAND: + if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) + { + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} + +#endif diff --git a/LogonHoursManager/app.ico b/LogonHoursManager/app.ico new file mode 100644 index 0000000..b3ec03b Binary files /dev/null and b/LogonHoursManager/app.ico differ diff --git a/LogonHoursManager/app.manifest b/LogonHoursManager/app.manifest new file mode 100644 index 0000000..3222184 --- /dev/null +++ b/LogonHoursManager/app.manifest @@ -0,0 +1,22 @@ + + + + + true + PerMonitorV2 + + + + + + + + + + diff --git a/LogonHoursManager/app.rc b/LogonHoursManager/app.rc new file mode 100644 index 0000000..7178f2f Binary files /dev/null and b/LogonHoursManager/app.rc differ diff --git a/LogonHoursManager/framework.h b/LogonHoursManager/framework.h new file mode 100644 index 0000000..3f59c93 --- /dev/null +++ b/LogonHoursManager/framework.h @@ -0,0 +1,2 @@ +#pragma once + diff --git a/LogonHoursManager/packages.config b/LogonHoursManager/packages.config new file mode 100644 index 0000000..1b7e886 --- /dev/null +++ b/LogonHoursManager/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/LogonHoursManager/resource.h b/LogonHoursManager/resource.h new file mode 100644 index 0000000..c6cac94 --- /dev/null +++ b/LogonHoursManager/resource.h @@ -0,0 +1,28 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by app.rc +// +#define IDD_MAIN 102 +#define IDS_APP_TITLE 103 +#define IDD_ABOUTBOX 103 +#define IDM_ABOUT 104 +#define IDS_WINDOW_CLASS 104 +#define IDM_EXIT 105 +#define IDI_APP 107 +#define IDC_ACCELERATORS 108 +#define IDM_APP 109 +#define IDR_MAINFRAME 128 +#define IDC_COMBO_USER 1000 +#define IDC_GROUP_HOURS 1002 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1004 +#define _APS_NEXT_SYMED_VALUE 110 +#endif +#endif diff --git a/LogonHoursManager/stdafx.cpp b/LogonHoursManager/stdafx.cpp new file mode 100644 index 0000000..6acde0d --- /dev/null +++ b/LogonHoursManager/stdafx.cpp @@ -0,0 +1,3 @@ +#include "stdafx.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/LogonHoursManager/stdafx.h b/LogonHoursManager/stdafx.h new file mode 100644 index 0000000..d55856d --- /dev/null +++ b/LogonHoursManager/stdafx.h @@ -0,0 +1,47 @@ +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef STDAFX_H_LogonHoursManager_ +#define STDAFX_H_LogonHoursManager_ + +#include "targetver.h" +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include + +#include // NetUserGetInfo(), etc. +#include // IsUserAnAdmin(), etc. +#include + +// C RunTime Header Files +#include +#include +#include +#include + +// STL +#include + +// ATL +#include + +// WTL +#include +extern CAppModule _Module; +#include // MSG_WM_INITDIALOG, etc. +#include // CToolTipCtrl +// #define _ATL_USE_DDX_FLOAT +#include +#include // CUpdateUI +#include + +#include +#include +#include +#include + +#include + +#endif diff --git a/LogonHoursManager/targetver.h b/LogonHoursManager/targetver.h new file mode 100644 index 0000000..5a9c7ff --- /dev/null +++ b/LogonHoursManager/targetver.h @@ -0,0 +1,6 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. +#include diff --git a/LogonHoursService/LogonHoursService.rc b/LogonHoursService/LogonHoursService.rc index 4fa8c80..1465378 100644 --- a/LogonHoursService/LogonHoursService.rc +++ b/LogonHoursService/LogonHoursService.rc @@ -10,7 +10,7 @@ #include #include "config.h" -#include "version.h" +#include "Common/version.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS @@ -37,7 +37,7 @@ BEGIN "#include \r\n" "\r\n" "#include ""config.h""\r\n" - "#include ""version.h""\r\n" + "#include ""Common/version.h""\r\n" "\0" END diff --git a/LogonHoursService/LogonHoursService.vcxproj b/LogonHoursService/LogonHoursService.vcxproj index 0cd1db4..648d720 100644 --- a/LogonHoursService/LogonHoursService.vcxproj +++ b/LogonHoursService/LogonHoursService.vcxproj @@ -30,22 +30,16 @@ - - + + - + <_ProjectFileVersion>15.0.28307.799 - - true - - - false - _CONSOLE;WIN32;_DEBUG;%(PreprocessorDefinitions) @@ -77,8 +71,7 @@ - Create - Create + Create diff --git a/LogonHoursService/WorkerThread.cpp b/LogonHoursService/WorkerThread.cpp index e989540..6b7bd20 100644 --- a/LogonHoursService/WorkerThread.cpp +++ b/LogonHoursService/WorkerThread.cpp @@ -1,5 +1,7 @@ #include "stdafx.h" +#include + #include "config.h" #include "Logger.h" #include "WorkerThread.h" @@ -28,98 +30,6 @@ LPSTR W2A(LPCWSTR lpwszStrIn) WorkerData g_WorkerData; -enum EDayOfWeek -{ - Sun, Mon, Tue, Wed, Thu, Fri, Sat, - FIRST = Sun, - LAST = Sat, -}; -EDayOfWeek& operator ++(EDayOfWeek& day) -{ - if (day < EDayOfWeek::LAST) - { - int value = static_cast(day); - day = static_cast(++value); - return day; - } - LOG_ERROR(__func__) << "Cannot increment " << int(day); - return day; -} - -class LogonHours -{ -public: - LogonHours(PBYTE usri2_logon_hours) - { - Init(usri2_logon_hours); - } - void Init(PBYTE usri2_logon_hours) - { - m_all = true; - int day = Sun; - int hour = 0; // 0:00 - for (int p = 0; p < 21; ++p) - { - const BYTE chunk = usri2_logon_hours[p]; - BYTE mask = 0b00000001; - for (int bit = 0; bit < 8; ++bit) - { - m_data[day][hour] = (chunk & mask) != 0; - if (!m_data[day][hour]) - m_all = false; - ++hour; - mask <<= 1; - if (hour >= 24) - { - ++day; - hour = 0; - } - } - } - } - - bool All() const { return m_all; } - bool Allowed(EDayOfWeek day, WORD hour) const - { - if (Sun <= day && day <= Sat && 0 <= hour && hour <= 23) - return m_data[day][hour]; - return false; - } - // @return seconds left (for the given time) until the allowed logon period expires, or -1 if no restrictions are set - // @note 0 (zero) is returned if logon isn't allowed during given time - long SecondsLeft(EDayOfWeek day, WORD hour, WORD minute, WORD second) const - { - if (All()) - return -1; - if (!Allowed(day, hour)) - return 0; - long left = (60 - second - 1) + 60 * (60 - minute - 1); // the rest of current hour is allowed - WORD hour_start = hour + 1; // for the current day - start with the next hour - EDayOfWeek d = day; - for (int i = 0; i < 7; ++i) // we'll check all week days starting from the current one - { - for (WORD h = hour_start; h < 24; ++h) // check hours until end of day - { - if (Allowed(day, h)) - left += 3600; - else - return left; - } - hour_start = 0; // for the rest of days - // next day - if (d == EDayOfWeek::LAST) - d = EDayOfWeek::FIRST; - else - ++d; - } - return left; - } - -private: - bool m_all; // true if no logon restrictions - bool m_data[7][24]; -}; - void seconds2time(long seconds, long* days, WORD* hours, WORD* mins, WORD* secs) { if (days) @@ -199,15 +109,9 @@ DWORD WINAPI WorkerThread(LPVOID lpData) { const auto& wusername = session.user; - LPBYTE bufptr; - // Refer to https://docs.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netusergetinfo - const NET_API_STATUS result = NetUserGetInfo(NULL, wusername.c_str(), 2, &bufptr); - if (result == NERR_Success) + LogonHours hours; + if (hours.InitFrom(wusername.c_str())) { - const USER_INFO_2* const userinfo2 = reinterpret_cast(bufptr); - const LogonHours hours(userinfo2->usri2_logon_hours); - NetApiBufferFree(bufptr); - if (!hours.All()) { const std::unique_ptr username(W2A(wusername.c_str())); diff --git a/LogonHoursService/main.cpp b/LogonHoursService/main.cpp index 0fa7aad..f620671 100644 --- a/LogonHoursService/main.cpp +++ b/LogonHoursService/main.cpp @@ -6,10 +6,6 @@ #include "ServiceManager.h" #include "WorkerThread.h" -#include "version.h" - -#pragma comment(lib, "ws2_32.lib") // Needed for log4cpp if PropertyConfigurator is used - void Main() { LOG_DEBUG(__func__) << "Entry"; @@ -56,91 +52,7 @@ int APIENTRY _tWinMain(HINSTANCE hInstance, { OutputDebugString(_T(SERVICE_NAME) _T(": Start\n")); - log4cpp::Category& root = log4cpp::Category::getRoot(); - - //////////////////////////////////////// - // Configigure log4cpp - //////////////////////////////////////// - char config_path[_MAX_PATH] = { 0 }; - GetModuleFileNameA(NULL, config_path, _countof(config_path)); - strcat_s(config_path, ".log4cpp"); - try - { - log4cpp::PropertyConfigurator::configure(config_path); - } - catch (const log4cpp::ConfigureFailure& /*ex*/) - { - config_path[0] = 0; -/* -#ifdef _CONSOLE - std::cerr - << ex.what() - << " [log4cpp::ConfigureFailure catched] while reading " - << file_log4cpp_init - << std::endl; -#endif -*/ - - // Default configuration for log4cpp -#ifdef _DEBUG - root.setPriority(log4cpp::Priority::DEBUG); -#else - root.setPriority(log4cpp::Priority::INFO); -#endif - - } - - const log4cpp::AppenderSet& set = root.getAllAppenders(); - if (set.empty()) // if no appenders are specified in the config file ... - { - char log_path[MAX_PATH]; - ExpandEnvironmentStringsA("%TEMP%\\" SERVICE_NAME ".log", log_path, _countof(log_path)); -#if 1 - if (log4cpp::Appender* const appender = new log4cpp::DailyRollingFileAppender("logfile", log_path, 14)) -#else - if (log4cpp::Appender* const appender = new log4cpp::RollingFileAppender("logfile", log_path, 10*1024*1024, 10)) -//#else - if (log4cpp::Appender* const appender = new log4cpp::FileAppender("logfile", log_path)) -#endif - { - log4cpp::PatternLayout* const layout = new log4cpp::PatternLayout(); - // Refer to http://www.cplusplus.com/reference/ctime/strftime/ for format specification - layout->setConversionPattern("%d{%Y.%m.%d %H:%M:%S,%l} [%p] [%t] %c: %m%n"); - appender->setLayout(layout); - root.addAppender(appender); - } - if (log4cpp::Appender* const appender = new log4cpp::Win32DebugAppender("debugger")) - { - log4cpp::PatternLayout* const layout = new log4cpp::PatternLayout(); - // Refer to http://www.cplusplus.com/reference/ctime/strftime/ for format specification - layout->setConversionPattern("%d{%H:%M:%S,%l} [%p] [%t] %c: %m%n"); - appender->setLayout(layout); - root.addAppender(appender); - } - } - - LOG_INFO(__func__) << "========================================"; - LOG_INFO(__func__) << "Log initialized successfully"; - if (*config_path) - LOG_INFO(__func__) << "Loaded " << config_path; - -#ifndef _CONSOLE - LOG_INFO(__func__) << "Length of command line: " << _tcslen(lpCmdLine); -#endif - - LOG_INFO(__func__) << "Version: " << VERSION_STRING; - LOG_INFO(__func__) << "Build : " << __DATE__ << ' ' << __TIME__; -#if defined(_MSC_FULL_VER) - LOG_INFO(__func__) << "_MSC_FULL_VER: " - << _MSC_FULL_VER / 10000000 << '.' - << (_MSC_FULL_VER % 10000000) / 100000 << '.' - << _MSC_FULL_VER % 100000; -#elif defined(_MSC_VER) - LOG_INFO(__func__) << "_MSC_VER: " - << _MSC_VER / 100 << '.' - << _MSC_VER % 100; -#endif - + LogMain(SERVICE_NAME); #ifdef _CONSOLE if (argc > 1) diff --git a/README.md b/README.md index de9a200..32889b8 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,38 @@ [![MSBuild](https://github.com/Anton-V-K/ClassicParentalControl/actions/workflows/msbuild.yml/badge.svg)](actions/workflows/msbuild.yml) + # Classic Parental Control (for Windows) The project provides utility which allows you to establish/maintain Parental Control for Local Accounts in Windows 10. -- `LogonHoursService` monitors allowed logon hours (which you can set with a command like `net user USERNAME /time:M-F,10-18`) and locks the session once the time is over + +- `LogonHoursManager` is a GUI application to easily change to change the allowed logon hours +- `LogonHoursService` it a service which monitors the allowed logon hours and locks the session once the time is over + +The allowed logon hours are the ones you can set with a command like `net user USERNAME /time:M-F,10-18`. ## Compilation -You need VS2019 with vc142 toolkit to build the solution. -The project can also be built with older versions of VS after tuning the properties: +You need VS2019 with v142 toolkit to build the solution. + +The project can also be built with other versions of VisualStuido after tuning the properties: 1. Copy `_props\user\_Platform.props.IN` to `_props\user\_Platform.props` + 2. Adjust `PlatformToolset` to specify available/desired toolset: - ``` - - v141 - - ``` + + ``` + + v141 + + ``` ## Installation + +### Manager + +The application doesn't require any special installation. + +### Service + 1. Copy all binaries from the archive into a directory with read-only access to Everyone, so only Administrators can remove or update them (if needed). 2. Run `LogonHoursService` from command line (with any permissions) for testing purposes, close its console window after ~5 seconds and examine the log in `%TEMP%\LogonHoursService.log`. The log should start with something like this: `2021.11.28 02:31:03,322 [INFO] [2672] wmain: ========================================` @@ -50,6 +65,12 @@ After local dumps are enabled, you will be able to find the dump under `%LOCALAP If you're running the service under `Local System` account, its dumps are generated under `%windir%\System32\config\systemprofile\AppData\Local\CrashDumps` or `%windir%\SysWOW64\config\systemprofile\AppData\Local\CrashDumps` (for 32-bit services on 64-bit system) +### Missing `clang_rt.asan_dbg_dynamic-i386.dll` in Debug configuration + +Both projects have now Address Sanitizer enabled in Debug configuration (refer to `true` in `\_props\Cxx.props`), so when running them not from VisualStudio IDE the system may complain about missing `clang_rt.asan_dbg_dynamic-i386.dll` (for Win32 platform). + +It is enough to copy the required DLL from VisualStudio platform toolset directory (something like `C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\Hostx86\x86`) to the directory with the executable. + ## Supported Windows versions This project is intended to be useful in Windows 10, where you cannot easily establish parental control for Local Accounts. @@ -61,6 +82,10 @@ Though the built-in classic parental control is available in Windows 7/8.1, you ## History +### 1.1.0 Alpha (10.05.2023) + +- [x] `LogonHoursManager` is available + ### 1.0.3 Alpha (28.01.2022) - [x] `DailyRollingFileAppender` is used instead of `RollingFileAppender` diff --git a/_props/Cxx.props b/_props/Cxx.props index b0b1095..b4d472b 100644 --- a/_props/Cxx.props +++ b/_props/Cxx.props @@ -1,11 +1,23 @@ + Unicode + + + true + + + + + $(MSBuildThisFileDirectory).. + + + EnableFastChecks @@ -17,6 +29,7 @@ + ProgramDatabase diff --git a/_props/Linker.props b/_props/Linker.props index c6d7acb..129cff4 100644 --- a/_props/Linker.props +++ b/_props/Linker.props @@ -1,15 +1,32 @@ - + + Windows + + /ignore:4099 %(AdditionalOptions) + + + + + MachineX86 + - Windows MachineX64 + + + false + + + + false + + diff --git a/_props/RC.props b/_props/RC.props new file mode 100644 index 0000000..22169f6 --- /dev/null +++ b/_props/RC.props @@ -0,0 +1,10 @@ + + + + + + $(MSBuildThisFileDirectory).. + + + + diff --git a/_props/TARGET_EXE.props b/_props/TARGET_EXE.props index 1ce2aa4..84aeaca 100644 --- a/_props/TARGET_EXE.props +++ b/_props/TARGET_EXE.props @@ -4,4 +4,5 @@ +