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 @@
+