Related
I created a shortcut in a Windows PC with a target path of:
C:\Users\b\Desktop\New Text Document.txt
Then I copied the shortcut to another PC with a different user name, and I want to retrieve the original target path.
If you open the shortcut file with a text editor, you can see the original path is preserved, so the goal is definitely possible.
The following code does not work, despite the presence of SLGP_RAWPATH. It outputs:
C:\Users\a\Desktop\New Text Document.txt
It is changing the user folder name to the one associated with the running program.
I understand that the problem is not about environment variables, because no environment variable name can be seen in the file. But I can't find any documentation about this auto-relocation behavior.
IShellLinkW*lnk;
if (CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID*)&lnk) == 0){
IPersistFile* file;
if (lnk->QueryInterface(IID_IPersistFile, (void**)&file) == 0){
if (file->Load(L"shortcut", 0) == 0){
wchar_t path[MAX_PATH];
if (lnk->GetPath(path, _countof(path), 0, SLGP_RAWPATH) == 0){
_putws(path);
}
IShellLinkDataList* sdl;
if (lnk->QueryInterface(IID_IShellLinkDataList, (void**)&sdl) == 0){
EXP_SZ_LINK* lnkData;
if (sdl->CopyDataBlock(EXP_SZ_LINK_SIG, (void**)&lnkData) == 0){
_putws(lnkData->swzTarget);
LocalFree(lnkData);
}
sdl->Release();
}
}
file->Release();
}
lnk->Release();
}
The Windows Shell Link class implements a property store, so you can get access to this with code like this (with ATL smart pointers):
int main()
{
// note: error checking omitted!
CoInitialize(NULL);
{
CComPtr<IShellLink> link;
link.CoCreateInstance(CLSID_ShellLink);
CComPtr<IPersistFile> file;
link->QueryInterface(&file);
file->Load(L"shortcut", STGM_READ);
// get the property store
CComPtr<IPropertyStore> ps;
link->QueryInterface(&ps);
// dump all properties
DWORD count = 0;
ps->GetCount(&count);
for (DWORD i = 0; i < count; i++)
{
PROPERTYKEY pk;
ps->GetAt(i, &pk);
// get property's canonical name from pk
CComHeapPtr<wchar_t> name;
PSGetNameFromPropertyKey(pk, &name);
PROPVARIANT pv;
PropVariantInit(&pv);
ps->GetValue(pk, &pv);
// convert PropVariants to a string to be able to display
CComHeapPtr<wchar_t> valueAsString;
PropVariantToStringAlloc(pv, &valueAsString); // propvarutil.h
wprintf(L"%s: %s\n", name, valueAsString);
PropVariantClear(&pv);
}
}
CoUninitialize();
return 0;
}
It will output this:
System.ItemNameDisplay: New Text Document.txt
System.DateCreated: 2021/06/03:14:45:30.000
System.Size: 0
System.ItemTypeText: Text Document
System.DateModified: 2021/06/03:14:45:29.777
System.ParsingPath: C:\Users\b\Desktop\New Text Document.txt
System.VolumeId: {E506CEB2-0000-0000-0000-300300000000}
System.ItemFolderPathDisplay: C:\Users\b\Desktop
So, you're looking for System.ParsingPath, which you can get directly like this:
...
ps->GetValue(PKEY_ParsingPath, &pv); // propkey.h
...
Your shortcut is a .lnk file, just without the .lnk file extension present. According to Microsoft's latest "Shell Link (.LNK) Binary File Format" documentation, your shortcut appears to be configured as a relative file target. The relative name is just New Text Document.txt. I didn't dig into the file too much, but I'm guessing that it is relative to the system's Desktop folder, so it will take on whatever the actual Desktop folder of the current PC is. Which would explain why querying the target changes the relative root from C:\Users\b\Desktop to C:\Users\a\Desktop when you change PCs.
As for being able to query the original target C:\Users\b\Desktop\New Text Document.txt, that I don't know. It is also present in the file, so in theory there should be a way to query it, but I don't know which field it is in, without taking the time to fully decode this file. You should try writing your own decoder, using the above documentation.
EDIT:
I have heavily edited this question after making some significant new discoveries and the question not having any answers yet.
Historically/AFAIK, keeping your Mac awake while in closed-display mode and not meeting Apple's requirements, has only been possible with a kernel extension (kext), or a command run as root. Recently however, I have discovered that there must be another way. I could really use some help figuring out how to get this working for use in a (100% free, no IAP) sandboxed Mac App Store (MAS) compatible app.
I have confirmed that some other MAS apps are able to do this, and it looks like they might be writing YES to a key named clamshellSleepDisabled. Or perhaps there's some other trickery involved that causes the key value to be set to YES? I found the function in IOPMrootDomain.cpp:
void IOPMrootDomain::setDisableClamShellSleep( bool val )
{
if (gIOPMWorkLoop->inGate() == false) {
gIOPMWorkLoop->runAction(
OSMemberFunctionCast(IOWorkLoop::Action, this, &IOPMrootDomain::setDisableClamShellSleep),
(OSObject *)this,
(void *)val);
return;
}
else {
DLOG("setDisableClamShellSleep(%x)\n", (uint32_t) val);
if ( clamshellSleepDisabled != val )
{
clamshellSleepDisabled = val;
// If clamshellSleepDisabled is reset to 0, reevaluate if
// system need to go to sleep due to clamshell state
if ( !clamshellSleepDisabled && clamshellClosed)
handlePowerNotification(kLocalEvalClamshellCommand);
}
}
}
I'd like to give this a try and see if that's all it takes, but I don't really have any idea about how to go about calling this function. It's certainly not a part of the IOPMrootDomain documentation, and I can't seem to find any helpful example code for functions that are in the IOPMrootDomain documentation, such as setAggressiveness or setPMAssertionLevel. Here's some evidence of what's going on behind the scenes according to Console:
I've had a tiny bit of experience working with IOMProotDomain via adapting some of ControlPlane's source for another project, but I'm at a loss for how to get started on this. Any help would be greatly appreciated. Thank you!
EDIT:
With #pmdj's contribution/answer, this has been solved!
Full example project:
https://github.com/x74353/CDMManager
This ended up being surprisingly simple/straightforward:
1. Import header:
#import <IOKit/pwr_mgt/IOPMLib.h>
2. Add this function in your implementation file:
IOReturn RootDomain_SetDisableClamShellSleep (io_connect_t root_domain_connection, bool disable)
{
uint32_t num_outputs = 0;
uint32_t input_count = 1;
uint64_t input[input_count];
input[0] = (uint64_t) { disable ? 1 : 0 };
return IOConnectCallScalarMethod(root_domain_connection, kPMSetClamshellSleepState, input, input_count, NULL, &num_outputs);
}
3. Use the following to call the above function from somewhere else in your implementation:
io_connect_t connection = IO_OBJECT_NULL;
io_service_t pmRootDomain = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMrootDomain"));
IOServiceOpen (pmRootDomain, current_task(), 0, &connection);
// 'enable' is a bool you should assign a YES or NO value to prior to making this call
RootDomain_SetDisableClamShellSleep(connection, enable);
IOServiceClose(connection);
I have no personal experience with the PM root domain, but I do have extensive experience with IOKit, so here goes:
You want IOPMrootDomain::setDisableClamShellSleep() to be called.
A code search for sites calling setDisableClamShellSleep() quickly reveals a location in RootDomainUserClient::externalMethod(), in the file iokit/Kernel/RootDomainUserClient.cpp. This is certainly promising, as externalMethod() is what gets called in response to user space programs calling the IOConnectCall*() family of functions.
Let's dig in:
IOReturn RootDomainUserClient::externalMethod(
uint32_t selector,
IOExternalMethodArguments * arguments,
IOExternalMethodDispatch * dispatch __unused,
OSObject * target __unused,
void * reference __unused )
{
IOReturn ret = kIOReturnBadArgument;
switch (selector)
{
…
…
…
case kPMSetClamshellSleepState:
fOwner->setDisableClamShellSleep(arguments->scalarInput[0] ? true : false);
ret = kIOReturnSuccess;
break;
…
So, to invoke setDisableClamShellSleep() you'll need to:
Open a user client connection to IOPMrootDomain. This looks straightforward, because:
Upon inspection, IOPMrootDomain has an IOUserClientClass property of RootDomainUserClient, so IOServiceOpen() from user space will by default create an RootDomainUserClient instance.
IOPMrootDomain does not override the newUserClient member function, so there are no access controls there.
RootDomainUserClient::initWithTask() does not appear to place any restrictions (e.g. root user, code signing) on the connecting user space process.
So it should simply be a case of running this code in your program:
io_connect_t connection = IO_OBJECT_NULL;
IOReturn ret = IOServiceOpen(
root_domain_service,
current_task(),
0, // user client type, ignored
&connection);
Call the appropriate external method.
From the code excerpt earlier on, we know that the selector must be kPMSetClamshellSleepState.
arguments->scalarInput[0] being zero will call setDisableClamShellSleep(false), while a nonzero value will call setDisableClamShellSleep(true).
This amounts to:
IOReturn RootDomain_SetDisableClamShellSleep(io_connect_t root_domain_connection, bool disable)
{
uint32_t num_outputs = 0;
uint64_t inputs[] = { disable ? 1 : 0 };
return IOConnectCallScalarMethod(
root_domain_connection, kPMSetClamshellSleepState,
&inputs, 1, // 1 = length of array 'inputs'
NULL, &num_outputs);
}
When you're done with your io_connect_t handle, don't forget to IOServiceClose() it.
This should let you toggle clamshell sleep on or off. Note that there does not appear to be any provision for automatically resetting the value to its original state, so if your program crashes or exits without cleaning up after itself, whatever state was last set will remain. This might not be great from a user experience perspective, so perhaps try to defend against it somehow, for example in a crash handler.
after creating a VSS snapshot I'd like to be able to query the USN journal. Is this possible or is the USN journal not accessible from a VSS snapshot?
my goal is to be able to use the USN journal in an incremental backup between two VSS snapshots. The process for the backup would be to
take a VSS Snapshot and backup the volume, taking note of the USN entries for each file
...use the filesystem, add/delete/modify files
take a second VSS snapshot, then use the USN journal to detect anything that changed during step #2
what I'm failing with right now is the part where I'm trying to get a hold of the highest USN entry on the VSS snapshot
create VSS Snapshot
open the snapshot with CreateFile(\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy25)
DeviceIoControl(FSCTL_QUERY_USN_JOURNAL) - this fails with GLE:1179 "the volume change journal is not active"
I can simulate this from the commandline as follows
C:\>vssadmin list shadows
vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool
(C) Copyright 2001-2005 Microsoft Corp.
Contents of shadow copy set ID: {54fc99fb-65f2-4558-8e12-9308979327f0}
Contained 1 shadow copies at creation time: 5/10/2012 6:44:19 PM
Shadow Copy ID: {a2d2c155-9916-47d3-96fd-94fae1c2f802}
Original Volume: (T:)\\?\Volume{a420b1fa-9744-11e1-9082-889ffaf52b70}\
Shadow Copy Volume: \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy25
Originating Machine: computer
Service Machine: computer
Provider: 'Microsoft Software Shadow Copy provider 1.0'
Type: Backup
Attributes: Differential
C:\>fsutil usn queryjournal \\?\Volume{a420b1fa-9744-11e1-9082-889ffaf52b70}
Usn Journal ID : 0x01cd2ebe9c795b57
First Usn : 0x0000000000000000
Next Usn : 0x000000000001b5f8
Lowest Valid Usn : 0x0000000000000000
Max Usn : 0x7fffffffffff0000
Maximum Size : 0x0000000000100000
Allocation Delta : 0x0000000000040000
C:\>fsutil usn queryjournal \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy25
Error: The volume change journal is not active.
Any ideas what I'm doing incorrectly, if this is possible?
This question was very important for the project I'm working on, so I finally got it (almost) 100% working.
Note : all of the below code snippets are in C#
Thanks to the previous answers from Hannes de Jager who pointed me to the right direction and documentations, I can now read an USN journal from a VSS snapshot or any other special device that the regular API cannot work with ; in my case, I mean VMware snapshots mounted using the VDDK (VMware SDK for VM disks).
I also reused or imported code coming from great projects :
USN journal explorer in C#, from StCroixSkipper (http://www.dreamincode.net/forums/blog/1017-stcroixskippers-blog/). Only reads USN using the official API (so no VSS here) but provides useful pinvokes and Win32 API structures as well as general information about how USN works
AlphaFS (https://github.com/alphaleonis/AlphaFS/) , which mimics large parts of the System.IO namespace, but allows to access special windows paths (VSS snapshots, raw devices) and provides useful extensions as well.
In case anyone else is interested, I share the code I use righ now, still in a rather crude state, but working.
How does it work?
First, you must acess the required Usn journal componenents. They are at the root of the device as ADS (alternate data streams) in an hidden entry. They cannot be accessed using the standard System.IO namespace, that's why I previously told I used the AlphaFS project, but pinvoking CreateFile() and ReadFile() should be enough.
1/2
The entry \$Extend\$UsnJrnl:$Max has global information about the current state of the journal. The most important parts are the usn journal ID (that you can use to check that the journal has not been reset if you want to compare multiple VSS snapshots) and the lowest valid USN journal sequence number.
USN journal structure:
// can be directly extracted from $MAX entry using Bitconverter.ToUint64
public struct USN_JOURNAL_DATA{
public UInt64 MaximumSize; //offset 0
public UInt64 AllocationDelta; // offset 8
public UInt64 UsnJournalID; // offset 16
public Int64 LowestValidUsn; // offset 24
}
2/2
The entry \$Extend\$UsnJrnl:$J contains the journal records. It is a sparse file so its disk usage is much lower than it size.
To answer the initial question, how can one know the Max used USN sequence from a previous VSS snapshot and compare it with that of another snapshot?
Well, the NextUsn value is simply equals to the size of the $Usnjrnl:$J entry.
On your "new" vss snapshot USN journal, you can seek to the "reference" VSS snapshot max USN before starting parsing records, if you want to parse the records changed between the two snapshots.
Generally speaking, each USN journal entry as a unique ID (USN number) which is the offset inside $J at whch the journal entry itself is located.
Each entry has a variable size, so to sequentially read then we have to calculate:
next entry offset inside $J =
offset of current entry (or its USN sequennce number + length of current entry
Fortunately the record length is a also a field of the USN entry record. Enough said, here is the USN record class:
public class UsnEntry : IComparable<UsnEntry>{
private const int FR_OFFSET = 8;
private const int PFR_OFFSET = 16;
private const int USN_OFFSET = 24;
private const int REASON_OFFSET = 40;
private const int FA_OFFSET = 52;
private const int FNL_OFFSET = 56;
private const int FN_OFFSET = 58;
public UInt32 RecordLength {get; private set;}
public Int64 USN {get; private set;}
public UInt64 FileReferenceNumber {get;private set;}
public UInt64 ParentFileReferenceNumber {get; private set;}
public UInt32 Reason{get; set;}
public string Name {get; private set;}
public string OldName{get; private set;}
private UInt32 _fileAttributes;
public bool IsFolder{
get{
bool bRtn = false;
if (0 != (_fileAttributes & Win32Api.FILE_ATTRIBUTE_DIRECTORY))
bRtn = true;
return bRtn;
}
}
public bool IsFile{
get{
bool bRtn = false;
if (0 == (_fileAttributes & Win32Api.FILE_ATTRIBUTE_DIRECTORY))
bRtn = true;
return bRtn;
}
}
/// <summary>
/// USN Record Constructor
/// </summary>
/// <param name="p">Buffer pointer to first byte of the USN Record</param>
public UsnEntry(IntPtr ptrToUsnRecord){
RecordLength = (UInt32)Marshal.ReadInt32(ptrToUsnRecord); //record size
FileReferenceNumber = (UInt64)Marshal.ReadInt64(ptrToUsnRecord, FR_OFFSET);
ParentFileReferenceNumber = (UInt64)Marshal.ReadInt64(ptrToUsnRecord, PFR_OFFSET);
USN = (Int64)Marshal.ReadInt64(ptrToUsnRecord, USN_OFFSET);
Reason = (UInt32)Marshal.ReadInt32(ptrToUsnRecord, REASON_OFFSET);
_fileAttributes = (UInt32)Marshal.ReadInt32(ptrToUsnRecord, FA_OFFSET);
short fileNameLength = Marshal.ReadInt16(ptrToUsnRecord, FNL_OFFSET);
short fileNameOffset = Marshal.ReadInt16(ptrToUsnRecord, FN_OFFSET);
Name = Marshal.PtrToStringUni(new IntPtr(ptrToUsnRecord.ToInt32() + fileNameOffset), fileNameLength / sizeof(char));
}
public int CompareTo(UsnEntry other){
return string.Compare(this.Name, other.Name, true);
}
public override string ToString(){
return string.Format ("[UsnEntry: RecordLength={0}, USN={1}, FileReferenceNumber={2}, ParentFileReferenceNumber={3}, Reason={4}, Name={5}, OldName={6}, IsFolder={7}, IsFile={8}", RecordLength, USN, (int)FileReferenceNumber, (int)ParentFileReferenceNumber, Reason, Name, OldName, IsFolder, IsFile);
}
}
I tried to isolate the smallest part of code that can parse an USN journal and extract its entries, starting from the lowest valid one. Remember, a record has a variable length; also note that some records point to a next record that is empty (the first 4 bytes, which are normally the record length, are zeroed). In this case I seek 4 bytes and retry parsing, until I get the next record. This behavior also have been reported by people having written similar parsing tools in Python so I guess I'n not too wrong here.
string vol = #"\\?\path_to_your_VSS_snapshot";
string maxHandle = vol + #"\$Extend\$UsnJrnl:$Max";
string rawJournal= vol + #"\$Extend\$UsnJrnl:$J";
// cannot use regular System.IO here, but pinvoking ReadFile() should be enough
FileStream maxStream = Alphaleonis.Win32.Filesystem.File.OpenRead(maxHandle);
byte[] maxData = new byte[32];
maxStream.Read(maxData, 0, 32);
//first valid entry
long lowestUsn = BitConverter.ToInt64(maxData, 24);
// max (last) entry, is the size of the $J ADS
IntPtr journalDataHandle = Win32Api.CreateFile(rawJournal,
0,
Win32Api.FILE_SHARE_READ| Win32Api.FILE_SHARE_WRITE,
IntPtr.Zero, Win32Api.OPEN_EXISTING,
0, IntPtr.Zero);
Win32Api.BY_HANDLE_FILE_INFORMATION fileInfo = new Win32Api.BY_HANDLE_FILE_INFORMATION();
Win32Api.GetFileInformationByHandle(journalDataHandle, out fileInfo);
Win32Api.CloseHandle(journalDataHandle);
long lastUsn = fileInfo.FileSizeLow;
int read = 0;
byte[] usnrecord;
byte[] usnraw = new byte[4]; // first byte array is to store the record length
// same here : pinvoke ReadFile() to avoid AlphaFS dependancy
FileStream rawJStream = Alphaleonis.Win32.Filesystem.File.OpenRead(rawJournal);
int recordSize = 0;
long pos = lowestUsn;
while(pos < newUsnState.NextUsn){
seeked = rawJStream.Seek(pos, SeekOrigin.Begin);
read = rawJStream.Read(usnraw, 0, usnraw.Length);
recordSize = BitConverter.ToInt32(usnraw, 0);
if(recordSize == 0){
pos = pos+4;
continue;
}
usnrecord = new byte[recordSize];
rawJStream.Read(usnrecord, 4, recordSize-4);
Array.Copy(usnraw, 0, usnrecord, 0, 4);
fixed (byte* p = usnrecord){
IntPtr ptr = (IntPtr)p;
// here we use the previously defined UsnEntry class
Win32Api.UsnEntry entry = new Win32Api.UsnEntry(ptr);
Console.WriteLine ("entry: "+entry.ToString());
ptr = IntPtr.Zero;
}
pos += recordSize;
}
Here are the pinvokes I use:
public class Win32Api{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BY_HANDLE_FILE_INFORMATION{
public uint FileAttributes;
public FILETIME CreationTime;
public FILETIME LastAccessTime;
public FILETIME LastWriteTime;
public uint VolumeSerialNumber;
public uint FileSizeHigh;
public uint FileSizeLow;
public uint NumberOfLinks;
/*public uint FileIndexHigh;
public uint FileIndexLow;*/
public FileID FileIndex;
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool
GetFileInformationByHandle(
IntPtr hFile,
out BY_HANDLE_FILE_INFORMATION lpFileInformation);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr
CreateFile(string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile);
}
This is definitely not the best piece of code on earth, but I think it will provide a good starting point for anyone having to do the same thing.
You may want to give Ruben's answer a second thought:
The USN Journal in a snapped volume is definitely readable by reading a special file inside the snapped VSS Volume. If the Windows API won't allow you to read the USN journal of a snapped volume then this may be a viable option although I'm sure it feels like a hack.
The thing is although NTFS does not have an open specification its been figured out by more than one project amongst which is the Linux implementations of NTFS drivers. The document that Ruben posted for you was originally written to help development of this driver.
Like I mentioned, the USN Journal content sits in a special file on your NTFS volume (like many things in NTFS e.g. The NTFS master file table. Actually it is said that everything in NTFS is a file). Special files in NTFS starts with a dollar sign $ and the one jou are looking for is named $UsnJrnl which in turn resides in a special directory named $Extend. So on your C: volume that file is
C:\$Extend\$UsnJrnl
or for you snapshot it would be
\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy25\$Extend\$UsnJrnl
The info you are looking for sits in an Alternate Data Stream named the $J stream and it has entries in this format (See Ruben's referred to doc):
Offset(in hex) Size Description
0x00 4 Size of entry
0x04 2 Major Version
0x06 2 Minor Version
0x08 8 MFT Reference
0x10 8 Parent MFT Reference
0x18 8 Offset of this entry in $J
0x20 8 Timestamp
0x28 4 Reason (see table below)
0x2B 4 SourceInfo (see table below)
0x30 4 SecurityID
0x34 4 FileAttributes
0x38 2 Size of filename (in bytes)
0x3A 2 Offset to filename
0x3C V Filename
V+0x3C P Padding (align to 8 bytes)
So you could be reading the $J stream of this special file to get the USN entry that you want. I'm wanting to tell you how to derive the USN number that you will need but I'm a bit rusty. If I figure it out again I will update this answer. But have a look at reading special files this way, its quite fun ;-). I've used this method to read the master file table (Special file $MFT) inside an unmounted VHD file in order to enumerate all the files on the volume inside the VHD.
I think that it is impossible to use the WinAPI interface to query the USN journal while the volume is not mounted.
You can try to open the file "$UsnJrnl" and manually parse the information you need.
See:
NTFS Documentation by Richard Russon and Yuval Fledel
Perhaps this may be of use: Journal-entries do not cross cluster-boundaries.
Every cluster ( normally 8 sectors per cluster) starts with a new entry. If towards the end of this cluster the next entry does not fit into the remaining cluster-space, this space is filled with zero, and the next entry ist stored at the start of the next cluster ( It's a pity this is not expressed in the "size of entry").
So you need not parse this space - just jump to the next cluster ( ! do not forget to use the RUN's of the journal to obtain the next valid disk-cluster).
Robert
Btw. You can use the USN (Offset of this entry in $J), the cluster-number and the position of this entry within the Cluster to check the validity of the entry.
I have minimum exposure to xcode and I/Okit framework. I have seen device descriptor and configuration descriptor of a usb device in USB prober.
I have written an xcode program using I/O kit framework which gives the usb device name as output, when we give product id and vendor id of that device as input.
/*Take the vendor and product id from console*/
printf("\nEnter the vendor id : ");
scanf("%lx",&usbVendor);
printf("\nEnter the product id :");
scanf("%lx",&usbProduct);
/* Set up a matching dictionary for the class */
matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
if (matchingDict == NULL)
{
return -1; // fail
}
// Create a CFNumber for the idVendor and set the value in the dictionary
numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usbVendor);
CFDictionarySetValue(matchingDict,
CFSTR(kUSBVendorID),
numberRef);
CFRelease(numberRef);
// Create a CFNumber for the idProduct and set the value in the dictionary
numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usbProduct);
CFDictionarySetValue(matchingDict,
CFSTR(kUSBProductID),
numberRef);
CFRelease(numberRef);
numberRef = NULL;
/*Get an iterator.*/
kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter);
if (kr != KERN_SUCCESS)
{
return -1;// fail
}
/* iterate */
while ((device = IOIteratorNext(iter)))
{
/*Display the device names */
io_name_t deviceName;
kr = IORegistryEntryGetName(device, deviceName);
if (KERN_SUCCESS != kr) {
deviceName[0] = '\0';
}
printf("\ndeviceName:%s",deviceName);
/*Free the reference taken before continuing to the next item */
IOObjectRelease(device);
}
/*Release the iterator */
IOObjectRelease(iter);
return 0;
}
I need to modify this, so that on giving vendor and product id of usb device, i will get the device descriptor and configuration descriptor( as shown in USB prober) as output.
Here i just gave an example, code can change but the output must be the descriptor( atleast the device decriptor).
Thanks in advance...
I think u should download the source code of USBProber rather than figure it out by yourself.
All the information presents in the USBProber u could get sooner or later by analyzing the source code.
Here is link to download the source code of IOUSBFamily, with USBProber inside it.
http://opensource.apple.com/tarballs/IOUSBFamily/
To get the configuration descriptors you can use code like this:
IOUSBDeviceInterface650** dev = ...;
IOUSBConfigurationDescriptor* configDesc = nullptr;
// Get the configuration descriptor for the first configuration (configuration 0).
kern_return_t kr = (*dev)->GetConfigurationDescriptorPtr(dev, 0, &configDesc);
if (kr != kIOReturnSuccess)
return an_error;
// Now use configDesc->...
Unfortunately there doesn't seem to be a function to get the device descriptor. There are functions to get some of it:
kr = (*dev)->GetDeviceClass(dev, &desc.bDeviceClass);
kr = (*dev)->GetDeviceSubClass(dev, &desc.bDeviceSubClass);
kr = (*dev)->GetDeviceProtocol(dev, &desc.bDeviceProtocol);
kr = (*dev)->GetDeviceVendor(dev, &desc.idVendor);
kr = (*dev)->GetDeviceProduct(dev, &desc.idProduct);
kr = (*dev)->GetDeviceReleaseNumber(dev, &desc.bcdDevice);
kr = (*dev)->GetNumberOfConfigurations(dev, &desc.bNumConfigurations);
But I don't see a way to get iManufacturer, iProduct, iSerial, bMaxPacketSize0, or bcdUSB.
There is a way around this - instead of using the built-in functions you can just do a control request to get the device descriptor (and configuration descriptors if you like) manually using a control transfer.
The USB 2.0 spec describes how to do this. Basically you:
Do a control transfer with bmRequestType = Device | Standard | In, bRequest = USB_GET_DESCRIPTOR_REQUEST, wValue = (USB_DEVICE_DESCRIPTOR_TYPE << 8), wIndex = 0, wLength = 2. That will fail because the descriptor is longer than 2, but it gets you the descriptor header which includes its length.
Repeat that request but with the correct length.
For configuration descriptors, do a third request with length wTotalLength.
You can do it with one less request since you know the size of the descriptors in advance, but I like to do it like that because then you can wrap it up in a very general getDescriptor() method.
In theory you can do it as simply as this:
IOUSBDeviceDescriptor devDesc;
IOUSBDevRequest request;
request.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
request.bRequest = kUSBRqGetDescriptor;
request.wValue = kUSBDeviceDesc << 8;
request.wIndex = 0;
request.wLength = sizeof(devDesc); // 18
request.pData = &devDesc;
request.wLenDone = 0;
kern_return_t kr = (*dev)->DeviceRequest(dev, &request);
But for some reason that is giving me a kIOUSBPipeStalled error. Not sure why.
Edit: I forgot the << 8. Now it works. :-)
The header IOKit/usb/USBSpec.h has a documented list of property keys corresponding to values inside the different descriptors. You can use those with IORegistryEntrySearchCFProperty (or similar functions) to get the descriptor values. This way you don't need a device request from an IOUSBDeviceInterface, which is advantageous because:
the documentation (comments) say that all device requests require an opened USB device and you may not have permission to do that for all devices (it's possible the documentation is wrong, at least for descriptor requests, but I have no guarantee of that and it seems better to follow it anyway)
device requests can block for an indeterminate amount of time
the manufacturer, product, and serial number strings (which are referenced by the device descriptor, but are not technically part of it) are not retrieved in this request
For getting device descriptor and configuration decriptor, we can use functions in IOUSBDeviceInterface class
Link: http://developer.apple.com/library/mac/#documentation/Darwin/Reference/IOKit/IOUSBLib_h/Classes/IOUSBDeviceInterface/index.html#//apple_ref/doc/com/intfm/IOUSBDeviceInterface/
For getting interface descriptor and end point descriptor, we can use functions in IOUSBInterfaceInterface class
Link: http://developer.apple.com/library/mac/#documentation/Darwin/Reference/IOKit/IOUSBLib_h/Classes/IOUSBInterfaceInterface/
The MSI stores the installation directory for the future uninstall tasks.
Using the INSTALLPROPERTY_INSTALLLOCATION property (that is "InstallLocation") works only the installer has set the ARPINSTALLLOCATION property during the installation. But this property is optional and almost nobody uses it.
How could I retrieve the installation directory?
Use a registry key to keep track of your install directory, that way you can reference it when upgrading and removing the product.
Using WIX I would create a Component that creates the key, right after the Directy tag of the install directory, declaration
I'd use MsiGetComponentPath() - you need the ProductId and a ComponentId, but you get the full path to the installed file - just pick one that goes to the location of your installation directory. If you want to get the value of a directory for any random MSI, I do not believe there is an API that lets you do that.
I would try to use Installer.OpenProduct(productcode). This opens a session, on which you can then ask for Property("TARGETDIR").
Try this:
var sPath = this.Context.Parameters["assemblypath"].ToString();
As stated elsewhere in the thread, I normally write a registry key in HKLM to be able to easily retrieve the installation directory for subsequent installs.
In cases when I am dealing with a setup that hasn't done this, I use the built-in Windows Installer feature AppSearch: http://msdn.microsoft.com/en-us/library/aa367578(v=vs.85).aspx to locate the directory of the previous install by specifying a file signature to look for.
A file signature can consist of the file name, file size and file version and other file properties. Each signature can be specified with a certain degree of flexibility so you can find different versions of the the same file for instance by specifying a version range to look for. Please check the SDK documentation: http://msdn.microsoft.com/en-us/library/aa371853(v=vs.85).aspx
In most cases I use the main application EXE and set a tight signature by looking for a narrow version range of the file with the correct version and date.
Recently I needed to automate Natural Docs install through Ketarin. I could assume it was installed into default path (%ProgramFiles(x86)%\Natural Docs), but I decided to take a safe approach. Sadly, even if the installer created a key on HKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall, none of it's value lead me to find the install dir.
The Stein answer suggests AppSearch MSI function, and it looks interesting, but sadly Natural Docs MSI installer doesn't provide a Signature table to his approach works.
So I decided to search through registry to find any reference to Natural Docs install dir, and I find one into HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components key.
I developed a Reg Class in C# for Ketarin that allows recursion. So I look all values through HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components and if the Main application executable (NaturalDocs.exe) is found into one of subkeys values, it's extracted (C:\Program Files (x86)\Natural Docs\NaturalDocs.exe becomes C:\Program Files (x86)\Natural Docs) and it's added to the system environment variable %PATH% (So I can call "NaturalDocs.exe" directly instead of using full path).
The Registry "class" (functions, actually) can be found on GitHub (RegClassCS).
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo("NaturalDocs.exe", "-h");
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
var process = System.Diagnostics.Process.Start (startInfo);
process.WaitForExit();
if (process.ExitCode != 0)
{
string Components = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components";
bool breakFlag = false;
string hKeyName = "HKEY_LOCAL_MACHINE";
if (Environment.Is64BitOperatingSystem)
{
hKeyName = "HKEY_LOCAL_MACHINE64";
}
string[] subKeyNames = RegGetSubKeyNames(hKeyName, Components);
// Array.Reverse(subKeyNames);
for(int i = 0; i <= subKeyNames.Length - 1; i++)
{
string[] valueNames = RegGetValueNames(hKeyName, subKeyNames[i]);
foreach(string valueName in valueNames)
{
string valueKind = RegGetValueKind(hKeyName, subKeyNames[i], valueName);
switch(valueKind)
{
case "REG_SZ":
// case "REG_EXPAND_SZ":
// case "REG_BINARY":
string valueSZ = (RegGetValue(hKeyName, subKeyNames[i], valueName) as String);
if (valueSZ.IndexOf("NaturalDocs.exe") != -1)
{
startInfo = new System.Diagnostics.ProcessStartInfo("setx", "path \"%path%;" + System.IO.Path.GetDirectoryName(valueSZ) + "\" /M");
startInfo.Verb = "runas";
process = System.Diagnostics.Process.Start (startInfo);
process.WaitForExit();
if (process.ExitCode != 0)
{
Abort("SETX failed.");
}
breakFlag = true;
}
break;
/*
case "REG_MULTI_SZ":
string[] valueMultiSZ = (string[])RegGetValue("HKEY_CURRENT_USER", subKeyNames[i], valueKind);
for(int k = 0; k <= valueMultiSZ.Length - 1; k++)
{
Ketarin.Forms.LogDialog.Log("valueMultiSZ[" + k + "] = " + valueMultiSZ[k]);
}
break;
*/
default:
break;
}
if (breakFlag)
{
break;
}
}
if (breakFlag)
{
break;
}
}
}
Even if you don't use Ketarin, you can easily paste the function and build it through Visual Studio or CSC.
A more general approach can be taken using RegClassVBS that allow registry key recursion and doesn't depend on .NET Framework platform or build processes.
Please note that the process of enumerating the Components Key can be CPU intense. The example above has a Length parameter, that you can use to show some progress to the user (maybe something like "i from (subKeysName.Length - 1) keys remaining" - be creative). A similar approach can be taken in RegClassVBS.
Both classes (RegClassCS and RegClassVBS) have documentation and examples that can guide you, and you can use it in any software and contribute to the development of them making a commit on the git repo, and (of course) opening a issue on it's github pages if you find any problem that you couldn't resolve yourself so we can try to reproduce the issue to figure out what we can do about it. =)