findfiles.sas
/* FindFiles for Windows
* Richard A. DeVenezia, Oct 2002
* http://www.devenezia.com
*/
options nosource nonotes;
filename FFAPI catalog 'WORK.FINDFILE.WINAPI.SOURCE';
data _null_;
file FFAPI;
input;
put _infile_;
cards4;
********************************************************************************
* FindFile.api
********************************************************************************
* Richard A. DeVenezia 4/20/98
********************************************************************************
* Define SAS Module function interfaces to functions in Windows kernel
* used to obtain filenames that match a search string containing wildcards * and ?
*
* You can only wildcard the last part of the path,
* I.e. C:\A\B\*.SAS is allowed, C:\*\B\*.SAS is not.
*
* The FindFirstFile interface is defined handle paths upto 260 characters,
* The FindFirstFile DLL by default accepts a string up to MAX_PATH (which is 260)
* characters. Any length string will be accepted by FindFirstFileW if prepended
* with \\?\
*
* FindFirstFile and FindNextFile return file information into a buffer that must
* be large enough to contain a LPWIN32_FIND_DATA structure, which is currently
* 320 bytes long
*
* The filename part of the structure, under Windows NT, is MAX_PATH (260) bytes
* Note: Using Explorer NT I could only create a filename with a total path length
* of 254 characters
* I.e. root level directory whose name is 245 characters long could only
* contain a file with a name 5 or less characters long.
* C:\<245 char dirname>\<5 char filename>, total length 254.
* Maybe assume implicit dot at end of names, takes it up to 256.
*
* Why MAX_PATH is 260 I do not know.
********************************************************************************
;
********************************************************************************
* Auxilliary Information
********************************************************************************
* from Winnt.h - File Attributes
*#define FILE_ATTRIBUTE_READONLY 0x00000001
*#define FILE_ATTRIBUTE_HIDDEN 0x00000002
*#define FILE_ATTRIBUTE_SYSTEM 0x00000004
*#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
*#define FILE_ATTRIBUTE_ARCHIVE 0x00000020
*#define FILE_ATTRIBUTE_NORMAL 0x00000080
*#define FILE_ATTRIBUTE_TEMPORARY 0x00000100
*#define FILE_ATTRIBUTE_COMPRESSED 0x00000800
********************************************************************************
;
*------------------------------------------------------------------------------;
ROUTINE FindFirstFileA
minarg=11
maxarg=11
stackpop=called
module=Kernel32
returns=long;
arg 1 char input format=$cstr260.; * LPCTSTR lpFileName, // address of name of file to search for ;
* LPWIN32_FIND_DATA lpFindFileData // address of returned information ;
arg 2 num output fdstart format=pib4.; * DWORD dwFileAttributes ;
arg 3 num output format=pib8.; * FILETIME ftCreationTime ;
arg 4 num output format=pib8.; * FILETIME ftLastAccessTime ;
arg 5 num output format=pib8.; * FILETIME ftLastWriteTime ;
arg 6 num output format=pib4.; * DWORD nFileSizeHigh ;
arg 7 num output format=pib4.; * DWORD nFileSizeLow ;
arg 8 num output format=pib4.; * DWORD dwReserved0 ;
arg 9 num output format=pib4.; * DWORD dwReserved1 ;
arg 10 char output format=$CHAR260.; * TCHAR cFileName[ MAX_PATH ] ;
arg 11 char output format=$CSTR14.; * TCHAR cfilename_altFileName[ 14 ] ;
*------------------------------------------------------------------------------;
ROUTINE FindNextFileA
minarg=11
maxarg=11
stackpop=called
module=Kernel32
returns=long;
arg 1 num input byvalue format=pib4.; * HANDLE hFindFile, // handle of search ;
* LPWIN32_FIND_DATA lpFindFileData // address of structure for data on found file;
arg 2 num output fdstart format=pib4.; * DWORD dwFileAttributes ;
arg 3 num output format=pib8.; * FILETIME ftCreationTime ;
arg 4 num output format=pib8.; * FILETIME ftLastAccessTime ;
arg 5 num output format=pib8.; * FILETIME ftLastWriteTime ;
arg 6 num output format=pib4.; * DWORD nFileSizeHigh ;
arg 7 num output format=pib4.; * DWORD nFileSizeLow ;
arg 8 num output format=pib4.; * DWORD dwReserved0 ;
arg 9 num output format=pib4.; * DWORD dwReserved1 ;
arg 10 char output format=$CHAR260.; * TCHAR cFileName[ MAX_PATH ] ;
arg 11 char output format=$CSTR14.; * TCHAR cfilename_altFileName[ 14 ] ;
*------------------------------------------------------------------------------;
ROUTINE FindClose
minarg=1
maxarg=1
stackpop=called
module=Kernel32
returns=long;
arg 1 num input byvalue format=pib4.; * HANDLE hFindFile // file search handle ;
*------------------------------------------------------------------------------;
ROUTINE FileTimeToLocalFileTime
minarg=2
maxarg=2
stackpop=called
module=Kernel32
returns=long;
arg 1 num input format=pib8.; * CONST FILETIME * lpFileTime, // pointer to UTC file time to convert ;
arg 2 num output format=pib8.; * LPFILETIME lpLocalFileTime // pointer to converted file time ;
*------------------------------------------------------------------------------;
ROUTINE FileTimeToSystemTime
minarg=9
maxarg=9
stackpop=called
module=Kernel32
returns=long;
arg 1 num input format=pib8.; * CONST FILETIME * lpFileTime, // pointer to file time to convert ;
* LPSYSTEMTIME lpSystemTime // pointer to structure to receive system time ;
arg 2 num output fdstart format=pib2.; * WORD wYear ;
arg 3 num output format=pib2.; * WORD wMonth ;
arg 4 num output format=pib2.; * WORD wDayOfWeek ;
arg 5 num output format=pib2.; * WORD wDay ;
arg 6 num output format=pib2.; * WORD wHour ;
arg 7 num output format=pib2.; * WORD wMinute ;
arg 8 num output format=pib2.; * WORD wSecond ;
arg 9 num output format=pib2.; * WORD wMilliseconds ;
*------------------------------------------------------------------------------;
ROUTINE
GetFileSecurityA
MODULE=advapi32
MINARG=5
MAXARG=5
STACKPOP=CALLED
RETURNS=LONG
;
arg 1 NUM BYVALUE FORMAT=IB4.; * lpFileName;
arg 2 NUM BYVALUE FORMAT=IB4.; * RequestedInformation;
arg 3 NUM BYVALUE FORMAT=IB4.; * pSecurityDescriptor;
arg 4 NUM BYVALUE FORMAT=IB4.; * nLength;
arg 5 NUM FORMAT=IB4.; * lpnLengthNeeded;
*------------------------------------------------------------------------------;
ROUTINE
GetSecurityDescriptorOwner
MODULE=advapi32
MINARG=3
MAXARG=3
STACKPOP=CALLED
RETURNS=LONG
;
arg 1 NUM BYVALUE FORMAT=IB4. ; * pSecurityDescriptor ;
arg 2 NUM FORMAT=IB4. ; * pOwner;
arg 3 NUM FORMAT=IB4. ; * lpbOwnerDefaulted;
*------------------------------------------------------------------------------;
ROUTINE
LookupAccountSidA
MODULE=advapi32
MINARG=7
MAXARG=7
STACKPOP=CALLED
RETURNS=LONG
;
arg 1 BYVALUE FORMAT=IB4. ; * lpSystemName;
arg 2 BYVALUE FORMAT=IB4. ; * Sid;
arg 3 BYVALUE FORMAT=IB4. ; * Name;
arg 4 BYADDR FORMAT=IB4. ; * cbName;
arg 5 BYVALUE FORMAT=IB4. ; * ReferencedDomainName;
arg 6 BYADDR FORMAT=IB4. ; * cbReferencedDomainName ;
arg 7 BYADDR FORMAT=IB4. ; * peUse;
;;;;
run;
filename FFAPI;
options notes;
%macro findfiles (
path=
, filespec=*
, out=
, recurse=N
, getOwner=N
, sdBuffLen=100
, nameBuffLen=100
, domainBuffLen=100
)
/ des='Find Files';
%* FindFiles for Windows
%* Richard A. DeVenezia, Oct 2002
%* http://www.devenezia.com
%* 11/03/02 RAD Add getOwner option
%*
%* This macro performs a Data step;
%local this mprint;
%let this = findfiles;
%let mprint = %sysfunc (getOption(MPRINT));
%let recurse = %upcase (&recurse);
%if &mprint = MPRINT %then options nomprint; ;
%if &recurse ne Y and &recurse ne N %then %do;
%put ERROR: &this: recurse must be N or Y.;
%goto EndMacro;
%end;
%if &getOwner ne Y and &getOwner ne N %then %do;
%put ERROR: &this: getOwner must be N or Y.;
%goto EndMacro;
%end;
%if %quote(&filespec) = %str () %then %do;
%put ERROR: &this: File specification is missing.;
%goto EndMacro;
%end;
%if %quote(&out) = %str () %then %do;
%put ERROR: &this: Output dataset name is missing.;
%goto EndMacro;
%end;
%if %quote(&path) = %str() %then %do;
%local p;
%let path = %sysfunc(reverse (&filespec));
%let p = %sysfunc (indexc (&path, :, \));
%if &p %then %do;
%let filespec = %sysfunc(reverse(%substr(&path,1,%eval(&p-1))));
%let path = %sysfunc(reverse(%substr(&path,%eval(&p))));
%end;
%else
%let path = .\;
%end;
%local mycbpath cbpath;
%let mycbpath = WORK.FINDFILE.WINAPI.SOURCE;
%let cbpath = %sysfunc (pathname(SASCBTBL));
%if %sysfunc (fileref(SASCBTBL)) <= 0 and %quote(&cbpath) ne %quote(&mycbpath) %then %do;
%* The robust thing would be to concatenate my API definitions,
%* or store the prior SASCBTBL fileref and replace it with mine
%* and on completion of macro restore the original SASCBTBL;
%put WARNING: &this: Fileref SASCBTBL changed to point to WORK.FINDFILE.WINAPI.SOURCE;
%put WARNING: &this: SASCBTBL had pointed to %sysfunc (pathname(SASCBTBL));
%end;
filename SASCBTBL catalog 'WORK.FINDFILE.WINAPI.SOURCE';
data &out ;
%if &recurse = Y %then %do;
array path_[0:50] $200 _temporary_;
array handle_[0:50] 8 _temporary_;
retain rIndex 0;
%end;
retain
path
filename_out
%if &getOwner = Y %then
owner
;
filetype
filename_alt
filesize
fmod
;
length filespec path filename_out $260 filetype $9;
length filename_alt $14;
%if &getOwner = Y %then
length owner $&nameBuffLen;
;
retain
handle p
fattr created accessed modified sizeh sizel reserve0 reserve1 lmod
filesize fmod finfo
year month dow day h m s ms rc
0 ;
path = %sysfunc(quote(&PATH));
filespec = trim(path) || %sysfunc(quote(&FILESPEC));
%if &recurse = Y %then %do;
Find:
nFolders + 1;
%end;
filename_out = repeat (' ', 259);
filename_alt = repeat (' ', 13);
handle = modulen ("FindFirstFileA", filespec,
fattr, created, accessed, modified, sizeH, sizeL,
reserve0, reserve1, filename_out, filename_alt);
if handle ne -1 then do;
do until (0 eq modulen ("FindNextFileA", handle,
fattr, created, accessed, modified, sizeH, sizeL,
reserve0, reserve1, filename_out, filename_alt)
);
p = index (filename_out, '00'x);
if p then filename_out = substr(filename_out,1,p-1);
if (band (fattr, 10x))
then filetype='Directory';
else filetype='File';
if filename_alt = "" then
filename_alt = filename_out;
filesize = 0ffffffffx * sizeH + sizeL;
rc = modulen ("FileTimeToLocalFileTime", modified, lmod);
rc = modulen ("FileTimeToSystemTime",
lmod,year,month,dow,day,h,m,s,ms);
fmod = dhms ( mdy (month,day,year), h,m,s ) + ms/1000;
if filename_out not in ('.' '..') then do;
%if &getOwner = Y %then %do;
link GetOwner;
%end;
OUTPUT;
end;
filename_out = repeat (' ', 259);
filename_alt = repeat (' ', 13);
end;
handle = modulen ("FindClose", handle);
end;
%if &recurse = Y %then %do;
rIndex + 1;
path_[rIndex] = path;
filespec = trim(path) || '*';
filename_out = repeat (' ', 259);
filename_alt = repeat (' ', 13);
handle_[rIndex] = modulen ("FindFirstFileA", filespec,
fattr, created, accessed, modified, sizeH, sizeL,
reserve0, reserve1, filename_out, filename_alt);
if handle_[rIndex] ne -1 then do;
do until (0 eq modulen ("FindNextFileA", handle_[rIndex],
fattr, created, accessed, modified, sizeH, sizeL,
reserve0, reserve1, filename_out, filename_alt)
);
p = index (filename_out, '00'x);
if p then filename_out = substr(filename_out,1,p-1);
if filename_out not in ('.' '..') and (band (fattr, 10x)) then
do;
path = trim(path) || trim(filename_out) || "\";
filespec = trim(path) || %sysfunc(quote(&FILESPEC));
goto Find;
rIndexR: ;
path = path_[rIndex];
end;
filename_out = repeat (' ', 259);
filename_alt = repeat (' ', 13);
end;
handle_[rIndex] = modulen ("FindClose", handle_[rIndex]);
end;
rIndex + (-1);
if rIndex then goto rIndexR;
if getOption ('NOTES') = 'NOTES' then
put "NOTE: &this: " nFolders "folders were searched.";
%end; %* recurse=Y;
stop;
%if &getOwner = Y %then %do;
GetOwner:
array sdBuff [&sdBuffLen] $1 _temporary_;
* determine how bytes needed to hold security info;
fullname = trim (path) || trim (filename_out) || "00"x;
sdSize=0;
rc = modulen ("GetFileSecurityA", addr(fullname), 01x, addr(sdBuff[1]), sdSize, sdSize);
if sdSize = 0 then do;
put "ERROR: &this: GetFileSecurity had an error.";
stop;
end;
if &sdBuffLen < sdSize then do;
put "ERROR: &this: SD buffer length is &sdBuffLen, need " sdSize;
stop;
end;
rc = modulen ("GetFileSecurityA", addr(fullname), 01x, addr(sdBuff[1]), sdSize, sdSize);
if rc = 0 then do;
put "ERROR: &this: GetFileSecurity had an error.";
stop;
end;
pOwner = 0;
flag = 0;
rc = modulen ("GetSecurityDescriptorOwner", addr(sdBuff[1]), pOwner, flag);
if rc = 0 then do;
put "ERROR: &this: GetSecurityDescriptorOwner had an error.";
stop;
end;
length owner $&nameBuffLen;
length domain $&domainBuffLen;
system = "00"x;
owner = "00"x;
name_len = 0;
domain = "00"x;
domain_len = 0;
use = 0;
rc = modulen ( "LookupAccountSidA", addr(system), pOwner, addr(owner), name_len, addr(domain), domain_len, use);
if &nameBuffLen < name_len then do;
put "ERROR: &this: Owner name buffer length is &nameBuffLen, need " name_len;
stop;
end;
if &domainBuffLen < domain_len then do;
put "ERROR: &this: Name buffer length is &domainBuffLen, need " domain_len;
stop;
end;
rc = modulen ( "LookupAccountSidA", addr(system), pOwner, addr(owner), name_len, addr(domain), domain_len, use);
domain = compress (domain, '00'x);
owner = compress (owner, '00'x);
return;
%end; %* GetOwner;
keep
path
filename_out
%if &getOwner = Y %then
owner
;
filetype
filename_alt
filesize
fmod
;
format
fmod datetime16.
filesize comma15.
;
informat
fmod datetime16.
filesize comma15.
;
rename
filename_out = name
filetype = type
filename_alt = altname
filesize = size
fmod = modified
;
run;
%EndMacro:
%if &mprint = MPRINT %then options mprint; ;
%mend findfiles;
options source;
/*
%findfiles (out=rad, filespec=c:\winnt\*.ini, getOwner=Y);
%findfiles (out=rad, filespec=c:\php\*.ini);
*/