Get OSX HD Serial Number - macos

I need to get the HD serial Number on OSX. I could not find any Delphi examples so far.
I found this C++ Builder example:
AnsiString GetSerialNumber()
{
AnsiString result;
io_service_t platformExpert =
IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("IOPlatformExpertDevice"));
if (platformExpert) {
CFTypeRef serialNumberAsCFString =
IORegistryEntryCreateCFProperty(platformExpert,
CFSTR(kIOPlatformSerialNumberKey),
kCFAllocatorDefault, 0);
if (serialNumberAsCFString)
{
result = CFStringGetCStringPtr((CFStringRef) serialNumberAsCFString, 0);
CFRelease(serialNumberAsCFString);
}
IOObjectRelease(platformExpert);
}
return result;
}
I'm using XE7.
Help porting this to Delphi will be highly appreciated.
#David - in Macapi.IOKit, IOServiceGetMatchingService point to CFDictionaryRef while IOServiceMatching point to CFMutableDictionaryRef.
I could not find any doc how to cast CFMutableDictionaryRef to CFDictionaryRef.
That's what I came up with so far:
function GetMacSerialNo: String;
Const
kIOPlatformSerialNumberKey = 'IOPlatformSerialNumber';
Var
PlatformExpert: io_service_t;
M: CFMutableDictionaryRef;
SerialNumberAsCFString: CFTypeRef;
_AnsiChar: PAnsiChar;
begin
M := IOServiceMatching('IOPlatformExpertDevice');
PlatformExpert := IOServiceGetMatchingService(kIOMasterPortDefault,M); --> E2010 Incompatible types: 'CFDictionaryRef' and 'CFMutableDictionaryRef'
SerialNumberAsCFString := IORegistryEntryCreateCFProperty(PlatformExpert,
CFSTR(kIOPlatformSerialNumberKey),kCFAllocatorDefault,0);
_AnsiChar := CFStringGetCStringPtr(SerialNumberAsCFString,0);
Result := String(AnsiString(_AnsiChar));
end;

Turns out casting a CFMutableDictionaryRef is simpler than I thought.
Here's the working code for anyone who may needs it.
Function GetMacSerialNo: String;
Const
kIOPlatformSerialNumberKey = 'IOPlatformSerialNumber';
Var
PlatformExpert: io_service_t;
M: CFMutableDictionaryRef;
SerialNumberAsCFString: CFTypeRef;
_AnsiChar: PAnsiChar;
begin
M := IOServiceMatching('IOPlatformExpertDevice');
PlatformExpert := IOServiceGetMatchingService(kIOMasterPortDefault,CFDictionaryRef(M));
SerialNumberAsCFString := IORegistryEntryCreateCFProperty(PlatformExpert,
CFSTR(kIOPlatformSerialNumberKey),kCFAllocatorDefault,0);
_AnsiChar := CFStringGetCStringPtr(SerialNumberAsCFString,0);
Result := String(AnsiString(_AnsiChar));
IOObjectRelease(PlatformExpert);
End;

Related

Take screenshot of a FMX form from another process, even if the form is not topmost / modal

I am currently trying to take a screenshot of a 2D Firemonkey (FMX) form from another process.
The following is using a well-known method based of GDI, using a BitBlt() to produce a bitmap, and WIC to save it to a PNG file.
{...}
uses
system.UITypes,
system.SysUtils,
system.Hash,
{$IF Defined(MSWINDOWS)}
winapi.Windows,
Winapi.Direct3D9,
WinApi.D3DX9,
Winapi.Wincodec,
WinApi.ActiveX,
{$ENDIF}
{ some internal units }
path.types,
path.utils,
os.utils;
{...}
Type
TWindowSnapshot = record
window_handle: THandle; // handle to window
process_id: Cardinal; // PID of the process
window_rect: TRect; // position of the window
title: string; // title
file_path: string; // path to the window's image file
end;
TWindowSnapshots = Tarray<TWindowSnapshot>;
procedure HRCHECK(r: HRESULT); inline;
begin
if r <> S_OK then abort;
end;
function pad(s,g:integer): integer; inline;
begin
dec(g);
result := (s + g) and not g;
end;
function WICSavePixelsToPng(r: TRect; stride: cardinal; pixels: PByte; filePath: string): HRESULT;
var
factory: IWICImagingFactory;
encoder: IWICBitmapEncoder;
frame: IWICBitmapFrameEncode;
encoderOptions: IPropertyBag2;
stream: IWICStream;
pf: TGUID;
coInit : HResult;
begin
result := E_INVALIDARG;
if not assigned(pixels) then exit;
if (filepath = '') then exit;
coInit := CoInitialize(nil);
pf := GUID_WICPixelFormat32bppBGRA;
result := CoCreateInstance(CLSID_WICImagingFactory, nil, CLSCTX_INPROC_SERVER, IWICImagingFactory, factory);
if result <> S_OK then exit;
result := factory.CreateStream(&stream);
if result <> S_OK then exit;
result := stream.InitializeFromFilename(PWideChar(filePath), GENERIC_WRITE);
if result <> S_OK then exit;
result := factory.CreateEncoder(GUID_ContainerFormatPng, TGUID.Empty, encoder);
if result <> S_OK then exit;
result := encoder.Initialize(stream, WICBitmapEncoderNoCache);
if result <> S_OK then exit;
result := encoder.CreateNewFrame(frame, encoderOptions);
if result <> S_OK then exit;
result := frame.Initialize(nil);
if result <> S_OK then exit;
result := frame.SetSize(r.width, r.height);
if result <> S_OK then exit;
result := frame.SetPixelFormat(pf);
if result <> S_OK then exit;
if pf <> GUID_WICPixelFormat32bppBGRA then
begin
assert(false,'ToDo');
end;
inc(pixels,(r.Left*4) + r.Top * stride);
result := frame.WritePixels(r.height, stride, stride * r.height, pixels);
if result <> S_OK then exit;
result := frame.Commit();
if result <> S_OK then exit;
result := encoder.Commit();
if (coInit <> 0) then
CoUninitialize();
end;
function take_window_snapshot_GDI(win_handle: THandle; PID: Cardinal; out cs: TWindowSnapshot; const image_folder: string): boolean;
var
Wr: TRect;
HdcSRC: HDC;
hDCDest: HDC;
hBmp: HBITMAP;
hOld: HGDIOBJ;
hFlags: Cardinal;
l: integer;
src_shot,dst_shot,lsrc,ldst: PByte;
sr: PRGBTriple;
dr: PRGBQuad;
buff_size: cardinal;
x,y, src_stride, dst_stride: integer;
bi: tagBITMAPINFO;
begin
result := false;
cs.window_handle := win_handle;
cs.title := '';
cs.window_rect := TRect.Empty;
cs.process_id := 0;
try
hFlags := GetWindowLong(win_handle,GWL_STYLE);
if (hFlags and (WS_DLGFRAME or WS_POPUP)) = 0 then exit;
if (hFlags and WS_VISIBLE) = 0 then exit;
GetWindowRect(win_handle,Wr);
if (Wr.Width = 0) or (Wr.Height = 0) then exit;
L := GetWindowTextLength(win_handle);
if L > 0 then
begin
setlength(cs.title,L);
GetWindowText(win_handle,pchar(cs.title),L+1);
end;
{ reserve memory for bitmaps }
src_stride := pad((wr.Width * 3),4);
dst_stride := pad((wr.Width * 4),4);
buff_size := src_stride * wr.Height;
getmem(src_shot,buff_size);
if not assigned(src_shot) then exit;
getmem(dst_shot,dst_stride * wr.Height);
if not assigned(src_shot) then
begin
freemem(src_shot);
exit;
end;
try
try
HdcSRC := GetWindowDC(win_handle);
HdcDest := CreateCompatibleDC(HdcSRC);
SetStretchBltMode(HdcDest, COLORONCOLOR);
HBmp := CreateCompatibleBitmap(HdcSRC,Wr.Width,wr.Height);
hOld := SelectObject(hDCDest,hBmp);
BitBlt(hDCDest,0,0,wr.width,wr.Height,HdcSRC,0,0,SRCCOPY);
{ target bitplane is 24 bits RGB }
bi := default(tagBITMAPINFO);
bi.bmiHeader.biSize := sizeof(TBitmapInfoHeader);
bi.bmiHeader.biWidth := wr.Width;
bi.bmiHeader.biHeight := wr.Height;
bi.bmiHeader.biPlanes := 1;
bi.bmiHeader.biBitCount := 24;
bi.bmiHeader.biCompression := BI_RGB;
GetDIBits(hDCDest,HBmp,0,wr.Height,src_shot,bi,DIB_RGB_COLORS);
{ now convert pixels to RGBA, in correct line order.
On Windows, last line is first in bitmap }
ldst := dst_shot;
lsrc := src_shot + (src_stride * wr.Height);
for y := 0 to wr.Height - 1 do
begin
dec(lsrc,src_stride);
sr := pointer(lsrc);
dr := pointer(ldst);
for x := 0 to wr.Width - 1 do
begin
dr.rgbBlue := sr.rgbtBlue;
dr.rgbRed := sr.rgbtRed;
dr.rgbGreen := sr.rgbtGreen;
dr.rgbReserved := 255;
inc(dr);
inc(sr);
end;
inc(ldst,dst_stride);
end;
{ save to PNG with WIC }
var tmp_name := Tpath.GetTempFileName('png',false,image_folder);
wr.Offset(-wr.Left,-wr.top);
if WICSavePixelsToPng(wr,dst_stride,dst_shot,tmp_name) = S_OK then
begin
result := true;
{ rename temp file name to new name if it doesn't exist already }
var new_name := Tpath.ExtractDirectory(tmp_name) + THashSHA1.GetHashStringFromFile(tmp_name) + '.png';
if not TPath.FileExists(new_name) then
begin
if Tfile.Rename(tmp_name,new_name) then
begin
cs.file_path := '';
end else begin
cs.file_path := new_name;
end;
end else begin
cs.file_path := new_name;
end;
end;
Tfile.Delete(tmp_name);
result := true;
finally
freemem(src_shot);
freemem(dst_shot);
end;
finally
SelectObject(hDCDest,hold);
DeleteDC(hDCDest);
ReleaseDC(win_handle,HdcSRC);
end;
except begin
result := false;
end;
end;
end;
This method is working for Delphi forms using VCL, however with forms using Firemonkey (FMX) 2D, it only produces bitmaps filled with BLACK (0) pixels.
I have tried an alternative using Direct3D9, that is working for Firemonkey form, however with a limitation I'll explain after the code snippet
function Take_window_snapshot_D3D(win_handle: THandle; PID: Cardinal; out cs: TWindowSnapshot; const image_folder: string): boolean;
var
D3D: IDirect3D9;
D3DDevice: IDirect3DDevice9;
mode: D3DDISPLAYMODE;
device: IDirect3DDevice9;
surface: IDirect3DSurface9;
parameters: D3DPRESENT_PARAMETERS;
rc: D3DLOCKED_RECT;
pitch: UINT;
shot: PByte;
shot_size: cardinal;
adapter: cardinal;
wr: TRect;
hCurWnd: THandle;
hCurThreadID: Cardinal;
L: integer;
hFlags: cardinal;
hr: HRESULT;
begin
result := false;
cs := default(TWindowSnapshot);
try
{ get the window type }
hFlags := GetWindowLong(win_handle,GWL_STYLE);
if (hFlags and (WS_DLGFRAME or WS_POPUP)) = 0 then exit;
if (hFlags and WS_VISIBLE) = 0 then exit;
{ size }
GetWindowRect(win_handle,Wr);
if (Wr.Width = 0) or (Wr.Height = 0) then exit;
{ title }
L := GetWindowTextLength(win_handle);
if L > 0 then
begin
setlength(cs.title,L);
GetWindowText(win_handle,pchar(cs.title),L+1);
end;
cs.window_handle := win_handle;
cs.window_rect := wr;
cs.process_id := PID;
{ Direct3D capture }
adapter := D3DADAPTER_DEFAULT;
D3D := Direct3DCreate9(D3D_SDK_VERSION);
HRCHECK(D3D.GetAdapterDisplayMode(adapter,mode));
parameters := default(D3DPRESENT_PARAMETERS);
parameters.Windowed := TRUE;
parameters.BackBufferCount := 1;
parameters.BackBufferHeight := mode.Height;
parameters.BackBufferWidth := mode.Width;
parameters.SwapEffect := D3DSWAPEFFECT_DISCARD;
parameters.hDeviceWindow := win_handle;
{ create device & capture surface }
HRCHECK(d3d.CreateDevice(adapter, D3DDEVTYPE_HAL, win_handle, D3DCREATE_SOFTWARE_VERTEXPROCESSING, #parameters, device));
{ create surface }
HRCHECK(device.CreateOffscreenPlainSurface(mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface, nil));
{ bring our window to front }
hCurWnd := GetForegroundWindow;
hCurThreadID := GetCurrentThreadId;
AttachThreadInput(PID,hCurThreadID,TRUE);
SetWindowPos(win_handle,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE or SWP_NOMOVE);
SetWindowPos(win_handle,HWND_NOTOPMOST,0,0,0,0,SWP_SHOWWINDOW or SWP_NOSIZE or SWP_NOMOVE);
SetForegroundWindow(win_handle);
SetFocus(win_handle);
SetActiveWindow(win_handle);
AttachThreadInput(PID,hCurThreadID,false);
{ Obtain stride value to allocate buffer }
HRCHECK(surface.LockRect(rc,nil,0));
pitch := rc.Pitch;
HRCHECK(surface.UnlockRect());
shot_size := pitch*mode.Height;
getmem(shot,shot_size);
if not assigned(shot) then abort;
try
{ get surface data }
HRCHECK(device.GetFrontBufferData(0, surface));
{ copy surface data }
HRCHECK(surface.LockRect(rc,nil,0));
move(rc.pBits^,shot^,shot_size);
HRCHECK(surface.UnlockRect());
{ save snapshot to file using WIC, using a temporary file name first }
var tmp_name := Tpath.GetTempFileName('png',false,image_folder);
if WICSavePixelsToPng(wr,pitch,shot,tmp_name) = S_OK then
begin
result := true;
{ rename temp file name to new name if it doesn't exist already }
var new_name := Tpath.ExtractDirectory(tmp_name) + THashSHA1.GetHashStringFromFile(tmp_name) + '.png';
if not TPath.FileExists(new_name) then
begin
if Tfile.Rename(tmp_name,new_name) then
begin
cs.file_path := '';
end else begin
cs.file_path := new_name;
end;
end else begin
cs.file_path := new_name;
end;
end;
Tfile.Delete(tmp_name);
finally
FreeMem(shot);
end;
except
result := false;
end;
end;
The limitation that I have with the D3D method is that in reality I don't take a snapshot of a specific window but I take a snapshot of the entire screen that I crop after to take only the window of my interest.
For that, I must bring first the target window to front, but in some case this may not be possible. For instance if the target form is behind a modal form, it might be partially or totally hidden, as one can see below
So, my question : Is it possible to take a snapshot / print a single Firemonkey 2D form from another process, even if the form is not modal / partially covered by another form.
(Sorry for an answer here rather than a comment, which I can't do.)
I know little about working with different processes, but do you realise that FMX has a snapshot ability built in?
TControl.MakeScreenshot returns a bitmap of the control (with child controls). So if you can call that from the other process and return the result, that might solve your problem.
However, this won't meet your needs if you literally need the whole form including the non-client area, as a form doesn't have that method. If the contents of the form are all you need, and you have a control (e.g. a TLayout) filling the form and containing all the child controls, you could use the .MakeScreenshot of that bottom control.
And yes, .MakeScreeshot works even if something else is covering the form.

How to show the content of a directory in File Explorer as thumbnails?

In a Delphi 10.4.2 win-32 VCL Application in Windows 10, I show the content of a directory in Windows File Explorer using this code and passing a path e.g. C:\MyDirectory\:
procedure ShellOpen(const Url: string; const Params: string = '');
begin
Winapi.ShellAPI.ShellExecute(0, 'Open', PChar(Url), PChar(Params), nil, SW_SHOWNORMAL);
end;
This works. But how can I force Explorer to show the files in this directory using THUMBNAILS? Are there any parameters for this that I could use in this procedure?
I have searched a lot for this but did not find anything.
You want to use the IFolderView::SetCurrentViewMode method.
Here is a C++ (using Visual Studio's ATL) example:
int main()
{
CoInitialize(NULL);
{
// get a shell item
CComPtr<IShellItem> folder;
ATLASSERT(SUCCEEDED(SHCreateItemFromParsingName(L"c:\\myPath1\myPath2", nullptr, IID_PPV_ARGS(&folder))));
// get its PIDL
CComHeapPtr<ITEMIDLIST> pidl;
ATLASSERT(SUCCEEDED(CComQIPtr<IPersistIDList>(folder)->GetIDList(&pidl)));
// open the item
SHELLEXECUTEINFO info = { };
info.cbSize = sizeof(info);
info.fMask = SEE_MASK_IDLIST;
info.nShow = SW_SHOW;
info.lpIDList = pidl;
ATLASSERT(ShellExecuteEx(&info));
// build a variant from the PIDL
UINT size = ILGetSize(pidl);
SAFEARRAY* psa = SafeArrayCreateVector(VT_UI1, 0, size);
CopyMemory(psa->pvData, pidl, size);
CComVariant v;
v.parray = psa;
v.vt = VT_ARRAY | VT_UI1;
// find the opened window
CComPtr<IShellWindows> windows;
ATLASSERT(SUCCEEDED(windows.CoCreateInstance(CLSID_ShellWindows)));
CComVariant empty;
long hwnd;
CComPtr<IDispatch> disp;
do
{
windows->FindWindowSW(&v, &empty, SWC_BROWSER, &hwnd, SWFO_NEEDDISPATCH, &disp);
if (disp)
break;
// we sleep for a while but using events would be better
// see https://stackoverflow.com/a/59974072/403671
Sleep(500);
} while (true);
// get IFolderView
CComPtr<IFolderView> view;
ATLASSERT(SUCCEEDED(IUnknown_QueryService(disp, IID_IFolderView, IID_PPV_ARGS(&view))));
// change view mode
ATLASSERT(SUCCEEDED(view->SetCurrentViewMode(FOLDERVIEWMODE::FVM_THUMBNAIL)));
}
CoUninitialize();
return 0;
}
Here's a Delphi version of the approach given by Simon Mourier:
uses
ComObj, ShellAPI, ShlObj, ActiveX, SHDocVw, ShLwApi;
function IUnknown_QueryService(punk: IUnknown; const guidService: TGUID;
const IID: TGUID; out Obj): HRESULT; stdcall; external 'ShLwApi'
name 'IUnknown_QueryService';
type
TFolderViewMode = (fvmAuto, fvmIcon, fvmSmallIcon, fvmList, fvmDetails,
fvmThumbnail, fvmTile, fvmThumbstrip, fvmContent);
procedure OpenFolder(AHandle: HWND; const AFolder: string; AViewMode: TFolderViewMode);
const
FolderViewModes: array[TFolderViewMode] of Cardinal =
(Cardinal(FVM_AUTO), FVM_ICON, FVM_SMALLICON, FVM_LIST, FVM_DETAILS,
FVM_THUMBNAIL, FVM_TILE, FVM_THUMBSTRIP, FVM_CONTENT);
var
ShellItem: IShellItem;
PIDL: PItemIDList;
SEInfo: TShellExecuteInfo;
ILSize: Cardinal;
SafeArray: PSafeArray;
v: OleVariant;
ShellWindows: IShellWindows;
ExplorerHWND: Integer;
disp: IDispatch;
view: IFolderView;
dummy: OleVariant;
begin
OleCheck(CoInitialize(nil));
try
OleCheck(SHCreateItemFromParsingName(PChar(AFolder), nil, IShellItem, ShellItem));
try
OleCheck((ShellItem as IPersistIDList).GetIDList(PIDL));
try
ZeroMemory(#SEInfo, SizeOf(SEInfo));
SEInfo.cbSize := SizeOf(SEInfo);
SEInfo.Wnd := AHandle;
SEInfo.fMask := SEE_MASK_IDLIST;
SEInfo.nShow := SW_SHOW;
SEInfo.lpIDList := PIDL;
Win32Check(ShellExecuteEx(#SEInfo));
ILSize := ILGetSize(PIDL);
SafeArray := SafeArrayCreateVector(VT_UI1, 0, ILSize);
CopyMemory(SafeArray.pvData, PIDL, ILSize);
PVariantArg(#v).vt := VT_ARRAY or VT_UI1;
PVariantArg(#v).parray := SafeArray;
OleCheck(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_LOCAL_SERVER,
IShellWindows, ShellWindows));
try
dummy := Unassigned;
var c: Integer := 0;
repeat
if c > 0 then
Sleep(200);
disp := ShellWindows.FindWindowSW(v, dummy, SWC_BROWSER, ExplorerHWND,
SWFO_NEEDDISPATCH);
Inc(c);
until Assigned(disp) or (c > 15);
if disp = nil then
Exit;
OleCheck(IUnknown_QueryService(disp, IFolderView, IFolderView, view));
try
OleCheck(view.SetCurrentViewMode(FolderViewModes[AViewMode]));
finally
view := nil;
end;
finally
ShellWindows := nil;
end;
finally
CoTaskMemFree(PIDL);
end;
finally
ShellItem := nil;
end;
finally
CoUninitialize;
end;
end;
Instead of sleep-polling indefinitely for the window (and potentially killing the application!), I give up after 3 seconds.
Example usage:
procedure TForm1.Button1Click(Sender: TObject);
begin
OpenFolder(Handle, 'C:\Users\Andreas Rejbrand\Skrivbord\Test', fvmThumbnail);
end;
The view modes,
type
TFolderViewMode = (fvmAuto, fvmIcon, fvmSmallIcon, fvmList, fvmDetails,
fvmThumbnail, fvmTile, fvmThumbstrip, fvmContent);
are mapped directly to Windows' FOLDERVIEWMODEs. Please note that your version of Windows might not support all of them.
No, EXPLORER.EXE has no parameter for this - it neither had one in Windows 7, nor does it have one in Windows 10. There are surprisingly few parameters available anyway.
Your best bet is starting the Explorer thru CreateProcessW() to then obtain the handle of its main thread and finally find the new window. Then you can manipulate individual controls, such as the file list. See this answer, based on AutoIt: Automating Windows Explorer - it basically uses IShellBrowser and (beyond Windows XP) IFolderView2.SetViewModeAndIconSize() to then apply FVM_THUMBNAIL.

How to get the FindData structure from the IShellItem2.GetProperty output in Delphi code?

I'm enumerating the Windows shell with IShellFolder and struggle with getting the FindData structure from the TPropVariant output of IShellItem2.GetProperty so that I can explore its content.
The question is: How do I get FindData from the TPropVariant output in Delphi code? C++ snippets don't help me in this case (that's why I'm posting, because there are several around that I haven't been able translate correctly.)
What I have is:
var
ShellItem2: IShellItem2;
ppropvar: TPropVariant;
HR: HResult;
FindData: TWin32FindData;
FileSize: Int64;
if ShellItem2.GetProperty(PKEY_FindData, ppropvar) = S_OK then
begin
//It's ok, then how do I get FindData?
//Calculate the file size, for instace.
FileSize := FindData.nFileSizeLow or Int64(FindData.nFileSizeHigh) shl 32;
end;
I can't find any formal documentation about how a WIN32_FIND_DATA is stored in a PROPVARIANT. However, based on a code snippet found in this Qt code patch, the last field of the PROPVARIANT holds a pointer to a WIN32_FIND_DATAW, so try something like this:
type
PWin32FindDataW = ^TWin32FindDataW;
PPWin32FindDataW = ^PWin32FindDataW;
var
ShellItem2: IShellItem2;
ppropvar: TPropVariant;
FindData: PWin32FindDataW;
FileSize: UInt64;
begin
...
if ShellItem2.GetProperty(PKEY_FindData, ppropvar) = S_OK then begin
FindData := PPWin32FindDataW(PByte(#ppropvar) + sizeof(ppropvar) - sizeof(Pointer))^;
// alternatively:
// FindData := PWin32FindDataW(ppropvar.caub.pElems);
if FindData <> nil then begin
FileSize := FindData.nFileSizeLow or (UInt64(FindData.nFileSizeHigh) shl 32);
...
end;
PropVariantClear(ppropvar);
end;
...
end;
function GetItemFindData(AItem: IShellItem2; out AFindData: TWin32FindDataW): Boolean;
var
PV: TPropVariant;
begin
Result := False;
PV.vt := VT_EMPTY;
if AItem.GetProperty(PKEY_FindData, PV) = S_OK then
begin
if (PV.vt = VT_UI1 or VT_VECTOR) and (PV.caub.cElems = SizeOf(AFindData)) and Assigned(PV.caub.pElems) then
begin
CopyMemory(#AFindData, PV.caub.pElems, SizeOf(AFindData));
Result := True;
end;
PropVariantClear(PV);
end;
end;

How to set a Windows folder's permissions using SID's in Delphi

Just now I'm looking at fixing a localization bug with a small application that get's fired during the install of a software package. The small application essentially brute forces permissions on our own folder within Application Data to set EVERYONE to full access.
The problem arises with EVERYONE not being localized. I know I need to use SID's, which for EVERYONE, is S-1-1-0. I can't find a WinAPI function for setting permissions using an SID.
The function just now uses BuildExplicitAccessWithName and SetNamedSecurityInfo as shown below
function setfullaccess(foldername:string):boolean; //B2415 MDE
var
pDACL: PACL;
pEA: PEXPLICIT_ACCESS_A;
R: DWORD;
begin
result := true;
pEA := AllocMem(SizeOf(EXPLICIT_ACCESS));
BuildExplicitAccessWithName(pEA, 'EVERYONE', GENERIC_ALL{GENERIC_READ},GRANT_ACCESS, SUB_CONTAINERS_AND_OBJECTS_INHERIT{NO_INHERITANCE});
R := SetEntriesInAcl(1, pEA, nil, pDACL);
if R = ERROR_SUCCESS then
begin
if SetNamedSecurityInfo(pchar(foldername), SE_FILE_OBJECT,DACL_SECURITY_INFORMATION, nil, nil, pDACL, nil) <> ERROR_SUCCESS then result := false;
LocalFree(Cardinal(pDACL));
end
else result := false;//ShowMessage('SetEntriesInAcl failed: ' + SysErrorMessage(R));
end;
Which functions should I be looking at using instead?
After some searching through the WinAPI documentation I went for the solution below. Essentially I use the SID to lookup the "readable" name and then use that. It won't be the most elegant solution but it works for me.
procedure TTestform.Button4Click(Sender: TObject);
var
Sid: PSID;
peUse: DWORD;
cchDomain: DWORD;
cchName: DWORD;
Name: array of Char;
Domain: array of Char;
pDACL: PACL;
pEA: PEXPLICIT_ACCESS_A;
R: DWORD;
foldername: String; //Temp to hardcode
begin
foldername := 'C:\TEMP'; //Temp to hardcode
Sid := nil;
Win32Check(ConvertStringSidToSidA(PChar('S-1-1-0'), Sid));
cchName := 0;
cchDomain := 0;
//Get Length
if (not LookupAccountSid(nil, Sid, nil, cchName, nil, cchDomain, peUse)) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) then
begin
SetLength(Name, cchName);
SetLength(Domain, cchDomain);
if LookupAccountSid(nil, Sid, #Name[0], cchName, #Domain[0], cchDomain, peUse) then
begin
pEA := AllocMem(SizeOf(EXPLICIT_ACCESS));
BuildExplicitAccessWithName(pEA, PChar(Name), GENERIC_ALL{GENERIC_READ},GRANT_ACCESS, SUB_CONTAINERS_AND_OBJECTS_INHERIT{NO_INHERITANCE});
R := SetEntriesInAcl(1, pEA, nil, pDACL);
if R = ERROR_SUCCESS then
begin
if SetNamedSecurityInfo(pchar(foldername), SE_FILE_OBJECT,DACL_SECURITY_INFORMATION, nil, nil, pDACL, nil) <> ERROR_SUCCESS then ShowMessage('SetNamedSecurityInfo failed: ' + SysErrorMessage(GetLastError));
LocalFree(Cardinal(pDACL));
end
else ShowMessage('SetEntriesInAcl failed: ' + SysErrorMessage(R));
end;
end;
end;
Alternative approach to avoid using both lookup and build
var SID: JwaWinNT.PSid; // JWA clashes with Delphi XE2 Windows.Windows.PSID type
pDACL: PACL;
EA: EXPLICIT_ACCESS;
SID_DATA: array[1..SECURITY_MAX_SID_SIZE] of byte;
SID_DATA_SIZE: DWORD;
SID_DATA_SIZE := Length(SID_DATA);
Pointer(SID) := #SID_DATA; // #SID_DATA[Low(SID_DATA)] for non-static arrays
pDACL := nil;
// if ConvertStringSidToSid(Auth_Users_SID, SID) then
if CreateWellKnownSid(WinAuthenticatedUserSid, nil, SID, SID_DATA_SIZE) then
try
EA.grfInheritance := SUB_CONTAINERS_AND_OBJECTS_INHERIT; // NO_INHERITANCE; // 0
EA.grfAccessMode := GRANT_ACCESS;
EA.grfAccessPermissions := GENERIC_ALL;
EA.Trustee.MultipleTrusteeOperation := NO_MULTIPLE_TRUSTEE;
EA.Trustee.pMultipleTrustee := nil;
EA.Trustee.TrusteeType := TRUSTEE_IS_WELL_KNOWN_GROUP;
EA.Trustee.TrusteeForm := TRUSTEE_IS_SID;
EA.Trustee.ptstrName := pointer(SID);
if ERROR_SUCCESS = SetEntriesInAcl(1, #EA, nil, pDACL) then begin
// optional creation of PATH and 0-bytes dummy file skipped
SetNamedSecurityInfo(pchar(foldername), SE_FILE_OBJECT, .... {see the author's answer above }
end;
finally
LocalFree(pDACL); // SID is not allocated on heap - no need to free it
end;
This snippet can also be checked against the unit at page #2 at http://forum.vingrad.ru/forum/topic-374131/0.html#st_15_view_0

Get the full name of a waveIn device

I have been using waveInGetDevCaps to get the name of waveIn devices, but the WAVEINCAPS structure only supports 31 characters plus a null, meaning that on my computer, the device names I get back are truncated:
Microphone / Line In (SigmaTel
Microphone Array (SigmaTel High,
I am sure that there must be a way of getting the full device name, but does anyone know what that is?
Yes, there's a workaround. I've solved this problem several times in shipping code.
Enumerate audio capture devices with DirectSoundCapture. The API is DirectSoundCaptureEnumerate. It will return you the full length name of the devices.
Of course, you're probably thinking "That's great, but the rest of my code is setup to use the Wave API, not DirectSound. I don't want to switch it all over. So how can I map the GUID IDs returned by DirectSoundCaptureEnumerate to the integer IDs used by the WaveIn API?"
The solution is to CoCreateInstance for the DirectSoundPrivate object (or call GetClassObject directly from dsound.dll) to get a pointer to an IKsPropertySet interface. From this interface, you can obtain the DSound GUID to Wave ID mapping. For more details see this web page:
http://msdn.microsoft.com/en-us/library/bb206182(VS.85).aspx
You want to use the DSPROPERTY_DIRECTSOUNDDEVICE_WAVEDEVICEMAPPING as described on the web page listed above.
Improved/full C# WPF code based on #Andrea Bertucelli answer
using NAudio.CoreAudioApi;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
foreach (KeyValuePair<string, MMDevice> device in GetInputAudioDevices())
{
Console.WriteLine("Name: {0}, State: {1}", device.Key, device.Value.State);
}
}
public Dictionary<string, MMDevice> GetInputAudioDevices()
{
Dictionary<string, MMDevice> retVal = new Dictionary<string, MMDevice>();
MMDeviceEnumerator enumerator = new MMDeviceEnumerator();
int waveInDevices = WaveIn.DeviceCount;
for (int waveInDevice = 0; waveInDevice < waveInDevices; waveInDevice++)
{
WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice);
foreach (MMDevice device in enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.All))
{
if (device.FriendlyName.StartsWith(deviceInfo.ProductName))
{
retVal.Add(device.FriendlyName, device);
break;
}
}
}
return retVal;
}
}
}
I completed the names of waveIn devices, exploring the names returned from MMDeviceEnumerator. For each waveIn device, when the name incompleted is part of full name of one of EnumerateAudioEndPoints, I used this full name for populate combobox in the same order of waveIn devices.
VisualBasic .NET:
Dim wain = New WaveIn()
Dim DeviceInfoI As WaveInCapabilities
Dim nomedevice As String
For de = 0 To wain.DeviceCount - 1
DeviceInfoI = wain.GetCapabilities(de)
nomedevice = DeviceInfoI.ProductName
For deg = 0 To devices.Count - 1
If InStr(devices.Item(deg).FriendlyName, nomedevice) Then
nomedevice = devices.Item(deg).FriendlyName
Exit For
End If
Next
cmbMessaggiVocaliMIC.Items.Add(nomedevice)
Next
cmbMessaggiVocaliMIC.SelectedIndex = 0
waveIn.DeviceNumber = cmbMessaggiVocaliMIC.SelectedIndex
There's a way involving the registry that's simpler than using DirectSound. If you use the WAVEINCAPS2 structure, it has a name GUID that references a key under HKLM\System\CurrentControlSet\Control\MediaCategories. If the key doesn't exist, then just use the name in the structure. This is documented on http://msdn.microsoft.com/en-us/library/windows/hardware/ff536382%28v=vs.85%29.aspx. Here's an example:
public static ICollection<AudioInputDevice> GetDevices()
{
RegistryKey namesKey = Registry.LocalMachine.OpenSubKey(#"System\CurrentControlSet\Control\MediaCategories");
List<AudioInputDevice> devices = new List<AudioInputDevice>();
for(int i=0, deviceCount=waveInGetNumDevs(); i<deviceCount; i++)
{
WAVEINCAPS2 caps;
if(waveInGetDevCaps(new IntPtr(i), out caps, Marshal.SizeOf(typeof(WAVEINCAPS2))) == 0 && caps.Formats != 0)
{
string name = null;
if(namesKey != null)
{
RegistryKey nameKey = namesKey.OpenSubKey(caps.NameGuid.ToString("B"));
if(nameKey != null) name = nameKey.GetValue("Name") as string;
}
devices.Add(new AudioInputDevice(name ?? caps.Name, caps.ProductGuid));
}
}
return devices;
}
struct WAVEINCAPS2
{
public short ManufacturerId, ProductId;
public uint DriverVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] public string Name;
public uint Formats;
public short Channels;
ushort Reserved;
public Guid ManufacturerGuid, ProductGuid, NameGuid;
}
[DllImport("winmm.dll")]
static extern int waveInGetDevCaps(IntPtr deviceId, out WAVEINCAPS2 caps, int capsSize);
[DllImport("winmm.dll", ExactSpelling=true)]
static extern int waveInGetNumDevs();
Looks like DirectSoundPrivate has some issues. I am trying to access it from an empty project and it works fine. However, when I try to access it from COM DLL or from a DLL thread it returns E_NOTIMPL error from IKsPropertySet::Get.
But I figured out another trick. It seems DirectSound enumerates capture and render devices in wave id order (excluding first default).
We still have to interact with old Wave API and it still lacks a proper way to do that. DirectShow provides audio input devices based on WaveIn and I need to get corresponding a WASAPI id and vice-versa.
Using NAudio, i use this code to get full device name...
using NAudio.CoreAudioApi;
using NAudio.Wave;
For getting all recording devices:
//create enumerator
var enumerator = new MMDeviceEnumerator();
//cycle through all audio devices
for (int i = 0; i < WaveIn.DeviceCount; i++)
Console.WriteLine(enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active)[i]);
//clean up
enumerator.Dispose();
For getting all capture devices:
//create enumerator
var enumerator = new MMDeviceEnumerator();
//cyckle trough all audio devices
for (int i = 0; i < WaveOut.DeviceCount; i++)
Console.WriteLine(enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active)[i]);
//clean up
enumerator.Dispose();
I have found another way using the registry to find audio devices' full name, both Input and Output.
Works on Windows 7 and Windows 10.
This method tries Adam M.'s approach in the first place. His method didn't work for me, but just in case it works for you I added as preferred method.
Delphi:
procedure TForm_Config.FormCreate(Sender: TObject);
type
tagWAVEOUTCAPS2A = packed record
wMid: WORD;
wPid: WORD;
vDriverVersion: MMVERSION;
szPname: array[0..MAXPNAMELEN-1] of AnsiChar;
dwFormats: DWORD;
wChannels: WORD;
wReserved1: WORD;
dwSupport: DWORD;
ManufacturerGuid: System.TGUID;
ProductGuid: System.TGUID;
NameGuid: System.TGUID;
end;
var
i,outdevs: Integer;
woCaps: tagWAVEOUTCAPS2A;
RegistryService: TRegistry;
iClasses, iSubClasses, iNames: Integer;
audioDeviceClasses, audioDeviceSubClasses, audioDeviceNames: TStringList;
initialDeviceName, partialDeviceName, fullDeviceName: string;
begin
audioDeviceClasses := TStringList.Create;
audioDeviceSubClasses := TStringList.Create;
audioDeviceNames := TStringList.Create;
try
RegistryService := TRegistry.Create;
try
RegistryService.RootKey := HKEY_LOCAL_MACHINE;
if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\') then begin
RegistryService.GetKeyNames(audioDeviceClasses);
RegistryService.CloseKey();
for iClasses := 0 to audioDeviceClasses.Count - 1 do begin
if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\'+audioDeviceClasses[iClasses]) then begin
RegistryService.GetKeyNames(audioDeviceSubClasses);
RegistryService.CloseKey();
for iSubClasses := 0 to audioDeviceSubClasses.Count - 1 do begin
if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\'+audioDeviceClasses[iClasses]+'\'+audioDeviceSubClasses[iSubClasses]) then begin
if RegistryService.ValueExists('DeviceDesc') then begin
fullDeviceName := Trim(RegistryService.ReadString('DeviceDesc'));
if AnsiPos(';',fullDeviceName) > 0 then begin
fullDeviceName := Trim(AnsiMidStr(fullDeviceName, AnsiPos(';',fullDeviceName)+1, Length(fullDeviceName)));
end;
audioDeviceNames.Add(fullDeviceName);
end;
RegistryService.CloseKey();
end;
end;
end;
end;
end;
finally
FreeAndNil(RegistryService);
end;
// WaveOutDevComboBox is a selection box (combo) placed in the form and will receive the list of output audio devices
WaveOutDevComboBox.Clear;
try
outdevs := waveOutGetNumDevs;
for i := 0 to outdevs - 1 do begin
ZeroMemory(#woCaps, sizeof(woCaps));
if waveOutGetDevCaps(i, #woCaps, sizeof(woCaps)) = MMSYSERR_NOERROR then begin
RegistryService := TRegistry.Create;
try
RegistryService.RootKey := HKEY_LOCAL_MACHINE;
if RegistryService.OpenKeyReadOnly('\System\CurrentControlSet\Control\MediaCategories\' + GUIDToString(woCaps.NameGuid)) then begin
WaveOutDevComboBox.Items.Add(RegistryService.ReadString('Name'));
RegistryService.CloseKey();
end
else begin
initialDeviceName := '';
partialDeviceName := Trim(woCaps.szPname);
if AnsiPos('(',partialDeviceName) > 0 then begin
initialDeviceName := Trim(AnsiLeftStr(partialDeviceName,AnsiPos('(',partialDeviceName)-1));
partialDeviceName := Trim(AnsiMidStr(partialDeviceName,AnsiPos('(',partialDeviceName)+1,Length(partialDeviceName)));
if AnsiPos(')',partialDeviceName) > 0 then begin
partialDeviceName := Trim(AnsiLeftStr(partialDeviceName,AnsiPos(')',partialDeviceName)-1));
end;
end;
for iNames := 0 to audioDeviceNames.Count - 1 do begin
fullDeviceName := audioDeviceNames[iNames];
if AnsiStartsText(partialDeviceName,fullDeviceName) then begin
break;
end
else begin
fullDeviceName := partialDeviceName;
end;
end;
WaveOutDevComboBox.Items.Add(initialDeviceName + IfThen(initialDeviceName<>EmptyStr,' (','') + fullDeviceName + IfThen(initialDeviceName<>EmptyStr,')',''));
end;
finally
FreeAndNil(RegistryService);
end;
end;
end;
except
WaveOutDevComboBox.Enabled := False;
end;
finally
FreeAndNil(audioDeviceClasses);
FreeAndNil(audioDeviceSubClasses);
FreeAndNil(audioDeviceNames);
end;
end;

Resources