mirror of
https://github.com/stascorp/rdpwrap.git
synced 2024-12-04 12:43:34 +00:00
61f6adf1f2
Fixed memory leak in the installer Added diagnostics info to configuration app
605 lines
15 KiB
ObjectPascal
605 lines
15 KiB
ObjectPascal
{
|
|
Copyright 2014 Stas'M Corp.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
}
|
|
|
|
unit MainUnit;
|
|
|
|
interface
|
|
|
|
uses
|
|
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
|
|
Dialogs, StdCtrls, Spin, ExtCtrls, Registry, WinSvc;
|
|
|
|
type
|
|
TMainForm = class(TForm)
|
|
bOK: TButton;
|
|
bCancel: TButton;
|
|
bApply: TButton;
|
|
cbSingleSessionPerUser: TCheckBox;
|
|
rgNLA: TRadioGroup;
|
|
cbAllowTSConnections: TCheckBox;
|
|
rgShadow: TRadioGroup;
|
|
seRDPPort: TSpinEdit;
|
|
lRDPPort: TLabel;
|
|
lService: TLabel;
|
|
lListener: TLabel;
|
|
lWrapper: TLabel;
|
|
lsListener: TLabel;
|
|
lsService: TLabel;
|
|
lsWrapper: TLabel;
|
|
Timer: TTimer;
|
|
lTSVer: TLabel;
|
|
lsTSVer: TLabel;
|
|
lWrapVer: TLabel;
|
|
lsWrapVer: TLabel;
|
|
bLicense: TButton;
|
|
gbDiag: TGroupBox;
|
|
procedure FormCreate(Sender: TObject);
|
|
procedure cbAllowTSConnectionsClick(Sender: TObject);
|
|
procedure seRDPPortChange(Sender: TObject);
|
|
procedure bApplyClick(Sender: TObject);
|
|
procedure bCancelClick(Sender: TObject);
|
|
procedure bOKClick(Sender: TObject);
|
|
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
|
|
procedure bLicenseClick(Sender: TObject);
|
|
procedure TimerTimer(Sender: TObject);
|
|
procedure FormDestroy(Sender: TObject);
|
|
private
|
|
{ Private declarations }
|
|
public
|
|
{ Public declarations }
|
|
procedure ReadSettings;
|
|
procedure WriteSettings;
|
|
end;
|
|
FILE_VERSION = record
|
|
Version: record case Boolean of
|
|
True: (dw: DWORD);
|
|
False: (w: record
|
|
Minor, Major: Word;
|
|
end;)
|
|
end;
|
|
Release, Build: Word;
|
|
bDebug, bPrerelease, bPrivate, bSpecial: Boolean;
|
|
end;
|
|
WTS_SESSION_INFOW = record
|
|
SessionId: DWORD;
|
|
Name: packed array [0..33] of WideChar;
|
|
State: DWORD;
|
|
end;
|
|
WTS_SESSION = Array[0..0] of WTS_SESSION_INFOW;
|
|
PWTS_SESSION_INFOW = ^WTS_SESSION;
|
|
|
|
const
|
|
winstadll = 'winsta.dll';
|
|
var
|
|
MainForm: TMainForm;
|
|
Ready: Boolean = False;
|
|
Arch: Byte;
|
|
OldWow64RedirectionValue: LongBool;
|
|
|
|
function WinStationEnumerateW(hServer: THandle;
|
|
var ppSessionInfo: PWTS_SESSION_INFOW; var pCount: DWORD): BOOL; stdcall;
|
|
external winstadll name 'WinStationEnumerateW';
|
|
function WinStationFreeMemory(P: Pointer): BOOL; stdcall; external winstadll;
|
|
|
|
implementation
|
|
|
|
{$R *.dfm}
|
|
{$R resource.res}
|
|
|
|
uses
|
|
LicenseUnit;
|
|
|
|
function ExpandPath(Path: String): String;
|
|
var
|
|
Str: Array[0..511] of Char;
|
|
begin
|
|
Result := '';
|
|
FillChar(Str, 512, 0);
|
|
if Arch = 64 then
|
|
Path := StringReplace(Path, '%ProgramFiles%', '%ProgramW6432%', [rfReplaceAll, rfIgnoreCase]);
|
|
if ExpandEnvironmentStrings(PWideChar(Path), Str, 512) > 0 then
|
|
Result := Str;
|
|
end;
|
|
|
|
function DisableWowRedirection: Boolean;
|
|
type
|
|
TFunc = function(var Wow64FsEnableRedirection: LongBool): LongBool; stdcall;
|
|
var
|
|
hModule: THandle;
|
|
Wow64DisableWow64FsRedirection: TFunc;
|
|
begin
|
|
Result := False;
|
|
hModule := GetModuleHandle(kernel32);
|
|
if hModule <> 0 then
|
|
Wow64DisableWow64FsRedirection := GetProcAddress(hModule, 'Wow64DisableWow64FsRedirection')
|
|
else
|
|
Exit;
|
|
if @Wow64DisableWow64FsRedirection <> nil then
|
|
Result := Wow64DisableWow64FsRedirection(OldWow64RedirectionValue);
|
|
end;
|
|
|
|
function RevertWowRedirection: Boolean;
|
|
type
|
|
TFunc = function(var Wow64RevertWow64FsRedirection: LongBool): LongBool; stdcall;
|
|
var
|
|
hModule: THandle;
|
|
Wow64RevertWow64FsRedirection: TFunc;
|
|
begin
|
|
Result := False;
|
|
hModule := GetModuleHandle(kernel32);
|
|
if hModule <> 0 then
|
|
Wow64RevertWow64FsRedirection := GetProcAddress(hModule, 'Wow64RevertWow64FsRedirection')
|
|
else
|
|
Exit;
|
|
if @Wow64RevertWow64FsRedirection <> nil then
|
|
Result := Wow64RevertWow64FsRedirection(OldWow64RedirectionValue);
|
|
end;
|
|
|
|
function GetFileVersion(const FileName: TFileName; var FileVersion: FILE_VERSION): Boolean;
|
|
type
|
|
VS_VERSIONINFO = record
|
|
wLength, wValueLength, wType: Word;
|
|
szKey: Array[1..16] of WideChar;
|
|
Padding1: Word;
|
|
Value: VS_FIXEDFILEINFO;
|
|
Padding2, Children: Word;
|
|
end;
|
|
PVS_VERSIONINFO = ^VS_VERSIONINFO;
|
|
const
|
|
VFF_DEBUG = 1;
|
|
VFF_PRERELEASE = 2;
|
|
VFF_PRIVATE = 8;
|
|
VFF_SPECIAL = 32;
|
|
var
|
|
hFile: HMODULE;
|
|
hResourceInfo: HRSRC;
|
|
VersionInfo: PVS_VERSIONINFO;
|
|
begin
|
|
Result := False;
|
|
|
|
hFile := LoadLibraryEx(PWideChar(FileName), 0, LOAD_LIBRARY_AS_DATAFILE);
|
|
if hFile = 0 then
|
|
Exit;
|
|
|
|
hResourceInfo := FindResource(hFile, PWideChar(1), PWideChar($10));
|
|
if hResourceInfo = 0 then
|
|
Exit;
|
|
|
|
VersionInfo := Pointer(LoadResource(hFile, hResourceInfo));
|
|
if VersionInfo = nil then
|
|
Exit;
|
|
|
|
FileVersion.Version.dw := VersionInfo.Value.dwFileVersionMS;
|
|
FileVersion.Release := Word(VersionInfo.Value.dwFileVersionLS shr 16);
|
|
FileVersion.Build := Word(VersionInfo.Value.dwFileVersionLS);
|
|
FileVersion.bDebug := (VersionInfo.Value.dwFileFlags and VFF_DEBUG) = VFF_DEBUG;
|
|
FileVersion.bPrerelease := (VersionInfo.Value.dwFileFlags and VFF_PRERELEASE) = VFF_PRERELEASE;
|
|
FileVersion.bPrivate := (VersionInfo.Value.dwFileFlags and VFF_PRIVATE) = VFF_PRIVATE;
|
|
FileVersion.bSpecial := (VersionInfo.Value.dwFileFlags and VFF_SPECIAL) = VFF_SPECIAL;
|
|
|
|
FreeLibrary(hFile);
|
|
Result := True;
|
|
end;
|
|
|
|
function IsWrapperInstalled(var WrapperPath: String): ShortInt;
|
|
var
|
|
TermServiceHost,
|
|
TermServicePath: String;
|
|
Reg: TRegistry;
|
|
begin
|
|
Result := -1;
|
|
WrapperPath := '';
|
|
Reg := TRegistry.Create;
|
|
Reg.RootKey := HKEY_LOCAL_MACHINE;
|
|
if not Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Services\TermService') then begin
|
|
Reg.Free;
|
|
Exit;
|
|
end;
|
|
TermServiceHost := Reg.ReadString('ImagePath');
|
|
Reg.CloseKey;
|
|
if Pos('svchost.exe', LowerCase(TermServiceHost)) = 0 then
|
|
begin
|
|
Result := 2;
|
|
Reg.Free;
|
|
Exit;
|
|
end;
|
|
if not Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Services\TermService\Parameters') then
|
|
begin
|
|
Reg.Free;
|
|
Exit;
|
|
end;
|
|
TermServicePath := Reg.ReadString('ServiceDll');
|
|
Reg.CloseKey;
|
|
Reg.Free;
|
|
if (Pos('termsrv.dll', LowerCase(TermServicePath)) = 0)
|
|
and (Pos('rdpwrap.dll', LowerCase(TermServicePath)) = 0) then
|
|
begin
|
|
Result := 2;
|
|
Exit;
|
|
end;
|
|
|
|
if Pos('rdpwrap.dll', LowerCase(TermServicePath)) > 0 then begin
|
|
WrapperPath := TermServicePath;
|
|
Result := 1;
|
|
end else
|
|
Result := 0;
|
|
end;
|
|
|
|
function GetTermSrvState: ShortInt;
|
|
type
|
|
SERVICE_STATUS_PROCESS = record
|
|
dwServiceType,
|
|
dwCurrentState,
|
|
dwControlsAccepted,
|
|
dwWin32ExitCode,
|
|
dwServiceSpecificExitCode,
|
|
dwCheckPoint,
|
|
dwWaitHint,
|
|
dwProcessId,
|
|
dwServiceFlags: DWORD;
|
|
end;
|
|
PSERVICE_STATUS_PROCESS = ^SERVICE_STATUS_PROCESS;
|
|
const
|
|
SvcName = 'TermService';
|
|
var
|
|
hSC: SC_HANDLE;
|
|
hSvc: THandle;
|
|
lpServiceStatusProcess: PSERVICE_STATUS_PROCESS;
|
|
Buf: Pointer;
|
|
cbBufSize, pcbBytesNeeded: Cardinal;
|
|
begin
|
|
Result := -1;
|
|
hSC := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT);
|
|
if hSC = 0 then
|
|
Exit;
|
|
|
|
hSvc := OpenService(hSC, PWideChar(SvcName), SERVICE_QUERY_STATUS);
|
|
if hSvc = 0 then
|
|
begin
|
|
CloseServiceHandle(hSC);
|
|
Exit;
|
|
end;
|
|
|
|
if QueryServiceStatusEx(hSvc, SC_STATUS_PROCESS_INFO, nil, 0, pcbBytesNeeded) then
|
|
Exit;
|
|
|
|
cbBufSize := pcbBytesNeeded;
|
|
GetMem(Buf, cbBufSize);
|
|
|
|
if not QueryServiceStatusEx(hSvc, SC_STATUS_PROCESS_INFO, Buf, cbBufSize, pcbBytesNeeded) then begin
|
|
FreeMem(Buf, cbBufSize);
|
|
CloseServiceHandle(hSvc);
|
|
CloseServiceHandle(hSC);
|
|
Exit;
|
|
end else begin
|
|
lpServiceStatusProcess := Buf;
|
|
Result := ShortInt(lpServiceStatusProcess^.dwCurrentState);
|
|
end;
|
|
FreeMem(Buf, cbBufSize);
|
|
CloseServiceHandle(hSvc);
|
|
CloseServiceHandle(hSC);
|
|
end;
|
|
|
|
function IsListenerWorking: Boolean;
|
|
var
|
|
pCount: DWORD;
|
|
SessionInfo: PWTS_SESSION_INFOW;
|
|
I: Integer;
|
|
begin
|
|
Result := False;
|
|
if not WinStationEnumerateW(0, SessionInfo, pCount) then
|
|
Exit;
|
|
for I := 0 to pCount - 1 do
|
|
if SessionInfo^[I].Name = 'RDP-Tcp' then begin
|
|
Result := True;
|
|
Break;
|
|
end;
|
|
WinStationFreeMemory(SessionInfo);
|
|
end;
|
|
|
|
function ExtractResText(ResName: String): String;
|
|
var
|
|
ResStream: TResourceStream;
|
|
Str: TStringList;
|
|
begin
|
|
ResStream := TResourceStream.Create(HInstance, ResName, RT_RCDATA);
|
|
Str := TStringList.Create;
|
|
try
|
|
Str.LoadFromStream(ResStream);
|
|
except
|
|
|
|
end;
|
|
ResStream.Free;
|
|
Result := Str.Text;
|
|
Str.Free;
|
|
end;
|
|
|
|
procedure TMainForm.ReadSettings;
|
|
var
|
|
Reg: TRegistry;
|
|
SecurityLayer, UserAuthentication: Integer;
|
|
begin
|
|
Reg := TRegistry.Create;
|
|
Reg.RootKey := HKEY_LOCAL_MACHINE;
|
|
Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Control\Terminal Server');
|
|
try
|
|
cbAllowTSConnections.Checked := not Reg.ReadBool('fDenyTSConnections');
|
|
except
|
|
|
|
end;
|
|
try
|
|
cbSingleSessionPerUser.Checked := Reg.ReadBool('fSingleSessionPerUser');
|
|
except
|
|
|
|
end;
|
|
Reg.CloseKey;
|
|
|
|
Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp');
|
|
seRDPPort.Value := 3389;
|
|
try
|
|
seRDPPort.Value := Reg.ReadInteger('PortNumber');
|
|
except
|
|
|
|
end;
|
|
SecurityLayer := 0;
|
|
UserAuthentication := 0;
|
|
try
|
|
SecurityLayer := Reg.ReadInteger('SecurityLayer');
|
|
UserAuthentication := Reg.ReadInteger('UserAuthentication');
|
|
except
|
|
|
|
end;
|
|
if (SecurityLayer = 0) and (UserAuthentication = 0) then
|
|
rgNLA.ItemIndex := 0;
|
|
if (SecurityLayer = 1) and (UserAuthentication = 0) then
|
|
rgNLA.ItemIndex := 1;
|
|
if (SecurityLayer = 2) and (UserAuthentication = 1) then
|
|
rgNLA.ItemIndex := 2;
|
|
try
|
|
rgShadow.ItemIndex := Reg.ReadInteger('Shadow');
|
|
except
|
|
|
|
end;
|
|
Reg.CloseKey;
|
|
Reg.Free;
|
|
end;
|
|
|
|
procedure TMainForm.WriteSettings;
|
|
var
|
|
Reg: TRegistry;
|
|
SecurityLayer, UserAuthentication: Integer;
|
|
begin
|
|
Reg := TRegistry.Create;
|
|
Reg.RootKey := HKEY_LOCAL_MACHINE;
|
|
Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server', True);
|
|
try
|
|
Reg.WriteBool('fDenyTSConnections', not cbAllowTSConnections.Checked);
|
|
except
|
|
|
|
end;
|
|
try
|
|
Reg.WriteBool('fSingleSessionPerUser', cbSingleSessionPerUser.Checked);
|
|
except
|
|
|
|
end;
|
|
Reg.CloseKey;
|
|
|
|
Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp', True);
|
|
try
|
|
Reg.WriteInteger('PortNumber', seRDPPort.Value);
|
|
except
|
|
|
|
end;
|
|
case rgNLA.ItemIndex of
|
|
0: begin
|
|
SecurityLayer := 0;
|
|
UserAuthentication := 0;
|
|
end;
|
|
1: begin
|
|
SecurityLayer := 1;
|
|
UserAuthentication := 0;
|
|
end;
|
|
2: begin
|
|
SecurityLayer := 2;
|
|
UserAuthentication := 1;
|
|
end;
|
|
else begin
|
|
SecurityLayer := -1;
|
|
UserAuthentication := -1;
|
|
end;
|
|
end;
|
|
if SecurityLayer >= 0 then begin
|
|
try
|
|
Reg.WriteInteger('SecurityLayer', SecurityLayer);
|
|
Reg.WriteInteger('UserAuthentication', UserAuthentication);
|
|
except
|
|
|
|
end;
|
|
end;
|
|
if rgShadow.ItemIndex >= 0 then begin
|
|
try
|
|
Reg.WriteInteger('Shadow', rgShadow.ItemIndex);
|
|
except
|
|
|
|
end;
|
|
end;
|
|
Reg.CloseKey;
|
|
Reg.Free;
|
|
end;
|
|
|
|
procedure TMainForm.TimerTimer(Sender: TObject);
|
|
var
|
|
WrapperPath: String;
|
|
FV: FILE_VERSION;
|
|
begin
|
|
case IsWrapperInstalled(WrapperPath) of
|
|
-1: begin
|
|
lsWrapper.Caption := 'Unknown';
|
|
lsWrapper.Font.Color := clGrayText;
|
|
end;
|
|
0: begin
|
|
lsWrapper.Caption := 'Not installed';
|
|
lsWrapper.Font.Color := clGrayText;
|
|
end;
|
|
1: begin
|
|
lsWrapper.Caption := 'Installed';
|
|
lsWrapper.Font.Color := clGreen;
|
|
end;
|
|
2: begin
|
|
lsWrapper.Caption := '3rd-party';
|
|
lsWrapper.Font.Color := clRed;
|
|
end;
|
|
end;
|
|
case GetTermSrvState of
|
|
-1, 0: begin
|
|
lsService.Caption := 'Unknown';
|
|
lsService.Font.Color := clGrayText;
|
|
end;
|
|
SERVICE_STOPPED: begin
|
|
lsService.Caption := 'Stopped';
|
|
lsService.Font.Color := clRed;
|
|
end;
|
|
SERVICE_START_PENDING: begin
|
|
lsService.Caption := 'Starting...';
|
|
lsService.Font.Color := clGrayText;
|
|
end;
|
|
SERVICE_STOP_PENDING: begin
|
|
lsService.Caption := 'Stopping...';
|
|
lsService.Font.Color := clGrayText;
|
|
end;
|
|
SERVICE_RUNNING: begin
|
|
lsService.Caption := 'Running';
|
|
lsService.Font.Color := clGreen;
|
|
end;
|
|
SERVICE_CONTINUE_PENDING: begin
|
|
lsService.Caption := 'Resuming...';
|
|
lsService.Font.Color := clGrayText;
|
|
end;
|
|
SERVICE_PAUSE_PENDING: begin
|
|
lsService.Caption := 'Suspending...';
|
|
lsService.Font.Color := clGrayText;
|
|
end;
|
|
SERVICE_PAUSED: begin
|
|
lsService.Caption := 'Suspended';
|
|
lsService.Font.Color := clWindowText;
|
|
end;
|
|
end;
|
|
if IsListenerWorking then begin
|
|
lsListener.Caption := 'Listening';
|
|
lsListener.Font.Color := clGreen;
|
|
end else begin
|
|
lsListener.Caption := 'Not listening';
|
|
lsListener.Font.Color := clRed;
|
|
end;
|
|
if WrapperPath = '' then begin
|
|
lsWrapVer.Caption := 'N/A';
|
|
lsWrapVer.Font.Color := clGrayText;
|
|
end else
|
|
if not GetFileVersion(ExpandPath(WrapperPath), FV) then begin
|
|
lsWrapVer.Caption := 'N/A';
|
|
lsWrapVer.Font.Color := clGrayText;
|
|
end else begin
|
|
lsWrapVer.Caption :=
|
|
IntToStr(FV.Version.w.Major)+'.'+
|
|
IntToStr(FV.Version.w.Minor)+'.'+
|
|
IntToStr(FV.Release)+'.'+
|
|
IntToStr(FV.Build);
|
|
lsWrapVer.Font.Color := clWindowText;
|
|
end;
|
|
if not GetFileVersion('termsrv.dll', FV) then begin
|
|
lsTSVer.Caption := 'N/A';
|
|
lsTSVer.Font.Color := clGrayText;
|
|
end else begin
|
|
lsTSVer.Caption :=
|
|
IntToStr(FV.Version.w.Major)+'.'+
|
|
IntToStr(FV.Version.w.Minor)+'.'+
|
|
IntToStr(FV.Release)+'.'+
|
|
IntToStr(FV.Build);
|
|
lsTSVer.Font.Color := clWindowText;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.bLicenseClick(Sender: TObject);
|
|
begin
|
|
LicenseForm.mText.Text := ExtractResText('LICENSE');
|
|
if LicenseForm.ShowModal <> mrOk then
|
|
Halt(0);
|
|
end;
|
|
|
|
procedure TMainForm.cbAllowTSConnectionsClick(Sender: TObject);
|
|
begin
|
|
if Ready then
|
|
bApply.Enabled := True;
|
|
end;
|
|
|
|
procedure TMainForm.seRDPPortChange(Sender: TObject);
|
|
begin
|
|
if Ready then
|
|
bApply.Enabled := True;
|
|
end;
|
|
|
|
procedure TMainForm.FormCreate(Sender: TObject);
|
|
var
|
|
SI: TSystemInfo;
|
|
begin
|
|
GetNativeSystemInfo(SI);
|
|
case SI.wProcessorArchitecture of
|
|
0: Arch := 32;
|
|
6: Arch := 64; // Itanium-based x64
|
|
9: Arch := 64; // Intel/AMD x64
|
|
else Arch := 0;
|
|
end;
|
|
if Arch = 64 then
|
|
DisableWowRedirection;
|
|
ReadSettings;
|
|
Ready := True;
|
|
end;
|
|
|
|
procedure TMainForm.FormDestroy(Sender: TObject);
|
|
begin
|
|
if Arch = 64 then
|
|
RevertWowRedirection;
|
|
end;
|
|
|
|
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
|
|
begin
|
|
if bApply.Enabled then
|
|
CanClose := MessageBox(Handle, 'Settings are not saved. Do you want to exit?',
|
|
'Warning', mb_IconWarning or mb_YesNo) = mrYes;
|
|
end;
|
|
|
|
procedure TMainForm.bOKClick(Sender: TObject);
|
|
begin
|
|
if bApply.Enabled then begin
|
|
WriteSettings;
|
|
bApply.Enabled := False;
|
|
end;
|
|
Close;
|
|
end;
|
|
|
|
procedure TMainForm.bCancelClick(Sender: TObject);
|
|
begin
|
|
Close;
|
|
end;
|
|
|
|
procedure TMainForm.bApplyClick(Sender: TObject);
|
|
begin
|
|
WriteSettings;
|
|
bApply.Enabled := False;
|
|
end;
|
|
|
|
end.
|