![]() |
SetSystemTime unter Windows 7
Hallo zusammen,
jetzt bitte nicht gleich stöhnen, dass diese Frage zum x-tausendsten Mal kommt. Ich programmiere übrigens mit Delphi 5 Pro. Ich möchte mal kurz meine Problematik erläutern: Ich weiß, dass ich die Systemzeit nur mit Administratorrechten ändern kann und das ist auch gut so. Erst mal zum Gesamtsystem. Es geht um ein digitales Steuerungssystem welches permanent über eine serielle Schnittstelle mit einem (meist Windows 7) PC verbunden ist. Auf diesem PC läuft eine kleine Anwendung in der Taskbar, welche die Kommunikation über die serielle Schnittstelle macht. Diese Anwendung kommuniziert mit mehreren anderen lokalen Anwendungen per Windows Messages. Das sind z.B. Anwendungen für Protokollierung oder Konfiguration dieses digitalen Systems. Alles läuft soweit unter einem normalen Benutzeraccount ohne Adminrechte wie es sich gehört. Der jeweils verwendete PC ist meistens alleine und ohne Netzwerkverbindung nur für dieses digitale System da und läuft 24h/7Tage. Die PC Anwendungen werden alle beim Windows Start per Autostart geladen für den Fall, das es trotz USV mal einen Aussetzer gibt. Das heißt auch, dass vor diesem PC nur in seltenen Fällen mal ein Benutzer sitzt. Nun zu meinem Problem. Das digitale System verfügt über eine eigene DCF-Uhr. Diese Uhrzeitinformation gibt sie auch per serieller Schnittstelle an die Anwendung in der Taskbar. Nun wäre es schön, wenn diese Anwendung eben auch die PC-Zeit entsprechend setzen könnte, damit die Zeiten des digitalen Systems und des PCs synchron sind, gerade auch wegen der Fehlerprotokollierung. Da die Anwendung unter einem normalen Benutzeraccount läuft hat sie nicht das Recht die PC-Zeit zu ändern. Eine kleine Zusatzanwendung mit Adminrechten zu starten fällt denke ich ebenfalls aus, da dann ja die UAC nach Erlaubnis fragen würde. Da aber kein Benutzer vor dem PC sitzt wird dies scheitern. Ich hab auch schon mal testweise die Anwendung in der Taskbar als Administrator laufen lassen. Dann kann sie zwar die Uhrzeit ändern (wenn ich per Token das entsprechende Privileg anfordere), aber kann dann nicht mehr per Windows Messages mit den anderen Anwendungen kommunizieren, da diese unter dem normalen Benutzeraccount laufen. Nach vielem Googlen tauchte öfter die Möglichkeit mit einem Windows Dienst auf. Dieser könnte ja mit dem lokalen Systemkonto theoretisch in der Lage sein, die Uhrzeit zu ändern. Nur wie sage ich dann z.B. dem Dienst "Hey, änder mal die Uhrzeit auf xxx"? Zumal ja die Anwendung und der Dienst unter verschiedenen Benutzerkonten arbeiten. Hat irgendjemand eine gute und einfache Idee wie man das realisieren könnte? Es muss auch nicht unbedingt über einen Dienst gehen. Wenn jemand eine bessere Idee hat, nur her damit. Wie gesagt darf zu diesem Zeitpunkt keine Windows Sicherheitsabfrage oder ähnliches kommen, da kein Benutzer vorm PC sitzt. Andererseits möchte ich auch Windows-konform bleiben und nicht alle Schutzmechanismen aushebeln müssen. Bin gespannt auf Eure Vorschläge. Bei komplizierteren Vorschlägen wäre ich auch dankbar für ein paar Codeschnipsel, da ich bisher eben nur die "normalen" Sachen programmiert habe. Ronny B. |
AW: SetSystemTime unter Windows 7
Du hast schon das Schlagwort gebracht: Windows Dienst.
Dieser frägt auch deine DCF-Uhr ab so das du auch keine Kommunikation zwischen "normaler" App und Dienste App (welche z.B. über Named Pipes gehen würde) nötig wäre. |
AW: SetSystemTime unter Windows 7
Vielen Dank für die schnelle Antwort.
Zitat:
Ich muss also die Daten an den Dienst übergeben, damit der die Uhrzeit setzen kann. viele Grüße Ronny B. Edit: Ich hab auch noch nie mit Pipes gearbeitet, aber funktioniert das den auch von einer Anwendung mit normalem Benutzeraccount zu einem Dienst mit lokalem Systemaccount? |
AW: SetSystemTime unter Windows 7
Zitat:
|
AW: SetSystemTime unter Windows 7
Aber der Dienst und meine Taskbar Anwendung können ja nicht gleichzeitig auf ein und den selben COM-Port zugreifen. Und die Taskbar-Anwendung und deren benutzter COM-Port kann ich nicht schließen, da auf der seriellen Schnittstelle ein permanenter Datenverkehr mit dem digitalen System läuft. Die Uhrzeitinformation ist nicht das einzige, was über die serielle Schnittstelle kommt. Der Rest wird halt von den anderen Anwendungen verarbeitet. Die Taskbar-Anwendung händelt sozusagen das Datenprotokoll auf der seriellen Schnittstelle. Jedes fertige Datenpaket wird dann von dieser Taskbar-Anwendung aufbereitet und an die entsprechende und dafür zuständige Anwendung per Windows Message verteilt. Eine Unterbrechung des Datenverkehrs würde auch zu einer Fehlermeldung des digitalen Systems führen.
Viele Grüße Ronny B. |
AW: SetSystemTime unter Windows 7
Zitat:
Wenn du hier im Forum nach "Named Pipe Dienst" suchst solltest du einiges an Beispiel(code) finden |
AW: SetSystemTime unter Windows 7
Danke erst einmal für die super schnelle Hilfe, das werde ich machen. Morgen bin ich nicht da, aber danach schaue ich mir das mal an und wenn noch fragen sind poste ich das.
Viele Grüße Ronny B. |
AW: SetSystemTime unter Windows 7
Nur mal so ins Blaue gedacht: Du könntest deine Anwendung als NTP-Server implementieren und die Windows-eigenen Internetzeit-Einstellungen auf den Localhost verbiegen.
|
AW: SetSystemTime unter Windows 7
Hallo,
den Datenaustausch habe ich bisher immer über Registry Einträge realisiert. Das Problem dabei ist, dass die Kommunikation über HKEY_CURRENT_USER erfolgen muss. Geht aber auch, ein Systemdienst kann Daten aus HKEY_CURRENT_USER lesen. Dazu muss die SID des Benutzers bekannt sein. Von einer Kommunikation über INI-Datei rate ich ab weil, je nach Intervall, zu viele Festplattenzugriffe das System ausbremsen könnten. Beispiel:
Delphi-Quellcode:
Lesen geht natürlich auch. Hab ich was vergessen ? Ach ja, dafür gibt es die Suchfunktion :wink:
procedure WriteUserRegistry;
var SID : PSID; strSID : PAnsiChar; s : String; err : DWORD; Registry : tRegistry; begin err := GetAccountSid('', 'Benutzername', SID); if err = 0 then begin if ConvertSidToStringSid(SID, strSID) then s := strSID else s := SysErrorMessage(err); end else s := SysErrorMessage(err); Registry:=Tregistry.Create; Registry.Rootkey := HKEY_USERS; try Registry.OPENKEY(s + '\Software\MeineZeit', true); Registry.WriteString('Zeit','10:00:59'); finally Registry.CloseKey; end; end; kuba |
AW: SetSystemTime unter Windows 7
So, ich wollte jetzt nur mal eine Rückmeldung geben.
Danke an Euch für die Tipps. @Uwe Raabe: Das mit dem NTP Server ist auch ein interessanter Gedanke. Bei Googlen hab ich allerdings immer nur Beispiele zu NTP Clients gefunden. @kuba: Das mit der Registry ist sicher ein einfacher Weg. Man muss nur sicherstellen, dass der Lesende nicht irgendeine alte Zeit ausliest. Ich hab das Ganze jetzt über einen Dienst und named Pipes gelöst (Dank an Bernhard Geyer). Um mir das Leben leicht zu machen und weil ich nicht so viel Zeit hatte mich in die Materie der Named Pipes einzuarbeiten hab ich die pipes.pas von Russel Jordan genommen. Gefunden hab ich die z.B. ![]()
Delphi-Quellcode:
Ich habe für den Stream den Typ Byte gewählt, weil meine Daten eh schon als Bytes vorliegen. Dabei hatte ich zuerst ein merkwürdiges Phänomen und zwar hatte ich zuerst als Typ Word genommen. Im OnMessage Ereignis hatte ich dann zwar die richtige Anzahl (Count). Wenn ich dann aber "Count" Anzahl Daten aus dem Stream gelesen habe, hatte ich danach nur die Hälfte der Daten, da jedes Lesen immer nur ein Byte geholt hatte. Na egal, hab's auf Typ Byte geändert und nun gehts.
procedure TMyTimeSettingService.PipeServer1PipeMessage(Sender: TObject;
Pipe: Cardinal; Stream: TStream); var TimeVar: TSystemTime; Daten: Array[0..1024] of Byte; Count: Integer; begin for Count := 0 to 16 do Daten[Count] := 0; Count := Stream.Size; Stream.Read(Daten[0],Count); if (Daten[0] = $59) then // Uhrzeitdaten beginnen bei mir mit $59 begin FillChar(TimeVar, SizeOf(TimeVar), 0); TimeVar.wYear := (Daten[1] *256) + Daten[2]; TimeVar.wMonth := (Daten[3] *256) + Daten[4]; TimeVar.wDayOfWeek := (Daten[5] *256) + Daten[6]; TimeVar.wDay := (Daten[7] *256) + Daten[8]; TimeVar.wHour := (Daten[9] *256) + Daten[10]; TimeVar.wMinute := (Daten[11] *256) + Daten[12]; TimeVar.wSecond := (Daten[13] *256) + Daten[14]; TimeVar.wMilliseconds := (Daten[15] *256) + Daten[16]; Daten[0] := $59; // Antwort an Sender Daten[1] := 1; if SetPCSystemTime(TimeVar) then Daten[2] := 1 // Status OK else Daten[2] := 0; // Status False PipeServer1.Write(Pipe,Daten,3); // 3 Bytes zurücksenden end; end; Zum endgültigen Setzen der Uhrzeit verwende ich dann folgenden Code:
Delphi-Quellcode:
Wie man sieht benutze ich die LogMessage Funktion, um bei Nichterfolg dieses zu Protokollieren. Dazu fand ich das
function SetPCSystemTime(dSysTime: TSystemTime): Boolean;
const SE_SYSTEMTIME_NAME = 'SeSystemtimePrivilege'; var hToken: THandle; ReturnLength: DWORD; tkp, PrevTokenPriv: TTokenPrivileges; luid: TLargeInteger; begin Result := False; if (Win32Platform = VER_PLATFORM_WIN32_NT) then begin if OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken) then begin try if not LookupPrivilegeValue(nil, SE_SYSTEMTIME_NAME, luid) then Exit; tkp.PrivilegeCount := 1; tkp.Privileges[0].luid := luid; tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; if not AdjustTokenPrivileges(hToken, False, tkp, SizeOf(TTOKENPRIVILEGES), PrevTokenPriv, ReturnLength) then Exit; if (GetLastError <> ERROR_SUCCESS) then begin LogMessage('Das angeforderte Recht zum Setzen der Uhrzeit wurde nicht gewährt. Meldung vom System: '+ SysErrorMessage(GetLastError) + '. ' + 'Prüfen Sie, ob der Dienst unter dem lokalen System-Konto ausgeführt wird.', EVENTLOG_ERROR_TYPE, 0, 4); Exit; end; finally CloseHandle(hToken); end; end; end; Result := Windows.SetLocalTime(dSysTime); end; ![]() ![]() Und der vollständigkeit halber: Installiert wird der Dienst bei mir zusammen mit der Taskbar-Anwendung mit Inno Setup. Dazu registriert bzw. deregistriert sich der Dienst selbst. Das Inno Setup Script dazu sieht so aus (ich hoffe der Code-Tag zeigt das halbwegs richtig an):
Code:
Da man bei einem Update den Dienst ja erst stoppen muss hab ich im Inno Script in der Code-Sektion das Beispiel von
[Run]
Filename: {app}\MyTimeService.exe; Parameters: " /install /silent"; WorkingDir: {app} [UninstallRun] Filename: {app}\MyTimeService.exe; Parameters: " /uninstall /silent"; WorkingDir: {app} ![]() Im Inno Script habe ich dann Pascal-Code für CurStepChanged und CurUninstallStepChanged eingefügt:
Code:
{#ServiceName} ist bei mir im Script als Konstante definiert und ist der Name (nicht der DisplayName) des Dienstes.
procedure CurStepChanged(CurrentStep: TSetupStep);
var I: Integer; Flag: Boolean; begin if CurrentStep = ssInstall then begin if IsServiceInstalled('{#ServiceName}') = true then begin if IsServiceRunning('{#ServiceName}') = true then begin Flag := false; StopService('{#ServiceName}'); // after stopping a service you should wait some seconds before removing // otherwise removing can fail ProgressPage.SetText('Stoppe den laufenden Dienst...', ''); ProgressPage.SetProgress(0, 0); ProgressPage.Show; try for I := 0 to 100 do begin ProgressPage.SetProgress(I, 100); Sleep(100); if IsServiceRunning('{#ServiceName}') = false then begin Flag := True; Sleep(100); Break; end; end; if not Flag then MsgBox('Der Dienst {#ServiceName} konnte nicht gestoppt werden!',mbInformation, MB_OK); finally ProgressPage.Hide; end; end; RemoveService('{#ServiceName}'); end; end; if CurrentStep = ssPostInstall then begin if IsServiceInstalled('{#ServiceName}') = true then begin Flag := false; StartService('{#ServiceName}'); ProgressPage.SetText('Starte den Dienst...', ''); ProgressPage.SetProgress(0, 0); ProgressPage.Show; try for I := 0 to 100 do begin ProgressPage.SetProgress(I, 100); Sleep(100); if IsServiceRunning('{#ServiceName}') = true then begin Flag := True; Sleep(100); Break; end; end; if not Flag then MsgBox('Der Dienst {#ServiceName} konnte nicht gestartet werden!',mbInformation, MB_OK); finally ProgressPage.Hide; end; end; end; end; procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); var I: Integer; Flag: Boolean; begin if CurUninstallStep = usUninstall then begin if IsServiceInstalled('{#ServiceName}') = true then begin if IsServiceRunning('{#ServiceName}') = true then begin Flag := false; StopService('{#ServiceName}'); // after stopping a service you should wait some seconds before removing // otherwise removing can fail for I := 0 to 100 do begin Sleep(100); if IsServiceRunning('{#ServiceName}') = false then begin Flag := True; Sleep(100); Break; end; end; if not Flag then MsgBox('Der Dienst {#ServiceName} konnte nicht gestoppt werden!',mbInformation, MB_OK); end; end; end; end; In meiner Systray-App kann ich jetzt sehr schön auch prüfen ob der Dienst läuft. Dazu hab ich das Beispiel ![]() Soweit alles gut. Ich hoffe meine Ausführungen zum Message Compiler und Inno Setup waren nicht zu sehr OT, aber ich denke das andere dieses vielleicht auch hilfreich finden. Eine kleine Frage hab ich dann aber doch noch: In meiner Systray-Anwendung möchte ich dem Nutzer die Möglichkeit geben über einen Button den Dienst auch mal zu stoppen und wieder zu starten. Könnte ja vielleicht bei Fehlereingrenzung hilfreich sein und möchte dann ungern den Benutzer per Telefon erst zu den Diensten lotsen müssen. Aber um Meine Dienst zu starten oder zu stoppen brauch ich ja Adminrechte, da der Dienst unter dem lokalen Systemkonto läuft. Also angenommen ich hab folgende Button-Click Methode:
Delphi-Quellcode:
Ist es auf einfachem Weg möglich die Funktion ServiceStop mit Adminrechten auszuführen? Die Windows UAC-Abfrage ist dabei kein Problem und soll auch kommen, da zu diesem Zeitpunkt ja auch ein Benutzer vor dem PC sitzt.
procedure TSubForm.Button2Click(Sender: TObject);
begin Label1.Caption := 'Warte...'; Application.ProcessMessages; if ServiceStop('','MyTimeSettingService') then Label1.Caption := 'OK' else Label1.Caption := 'Fehler'; Label2.Caption := IntToStr(ServiceGetStatus('','MyTimeSettingService')); end; Vielen Dank Ronny |
Alle Zeitangaben in WEZ +1. Es ist jetzt 00:45 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz