Not sure if this may help you at all, considering you have so many file to process..
In case you do need to call the Windows APIs directly like Mehrdad explained here, you could try incorporating the code below and use that instead of the get-ntfsowner
to see if it works any faster.
Also I would suggest putting this part of your code
$Properties = @{
Path = $Path
Owner = $Owner
}
one line down, so just after the
If ($Owner -ne "BUILTIN\Administrators" -and $Owner -ne $null-and $Owner -ne "Domain\Domain Admins" -and $Owner -notlike "S-1*")
It won't do much of course, but it saves you from creating a hash when it is not needed.
Maybe the test itself can be tidied up a bit, for instance something like:
If (($Owner) -and $Owner -notmatch '^(BUILTIN\\|NT AUTHORITY\\|S-1).+|(.+\\Domain Admins)$')
Anyway, here's the code using Win32 api to overcome the 260 character limit of Get-Ace
if (-not ([System.Management.Automation.PSTypeName]'Win32Api.LongFileName').Type) {
Add-Type -Language CSharp -TypeDefinition @"
using System;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.Security.Principal;
namespace Win32Api {
public static class LongFileName
{
internal const uint ERROR_SUCCESS = 0;
internal const int INVALID_FILE_ATTRIBUTES = -1;
internal const int FILE_ATTRIBUTE_READONLY = 0x1; // A file that is read-only. This attribute is not honored on directories.
internal const int FILE_ATTRIBUTE_HIDDEN = 0x2; // The file or directory is hidden. It is not included in an ordinary directory listing.
internal const int FILE_ATTRIBUTE_SYSTEM = 0x4; // A file or directory that the operating system uses a part of, or uses exclusively.
internal const int FILE_ATTRIBUTE_NORMAL = 0x80; // A file that does not have other attributes set. This attribute is valid only when used alone.
internal const int FILE_ATTRIBUTE_DIRECTORY = 0x10; // The handle that identifies a directory.
internal const int FILE_ATTRIBUTE_ARCHIVE = 0x20; // A file or directory that is an archive file or directory.
internal const int FILE_READ_ATTRIBUTES = 0x0080;
internal const int FILE_WRITE_ATTRIBUTES = 0x0100;
internal const int FILE_READ_DATA = 0x0001;
internal const int FILE_WRITE_DATA = 0x0002;
internal const int FILE_APPEND_DATA = 0x0004;
internal const int FILE_READ_EA = 0x0008;
internal const int FILE_WRITE_EA = 0x0010;
internal const int FILE_SHARE_NONE = 0x00000000;
internal const int FILE_SHARE_READ = 0x00000001;
internal const long READ_CONTROL = 0x00020000L;
internal const long STANDARD_RIGHTS_READ = READ_CONTROL;
internal const long STANDARD_RIGHTS_WRITE = READ_CONTROL;
internal const long SYNCHRONIZE = 0x00100000L;
internal const long FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE |
FILE_WRITE_DATA |
FILE_WRITE_ATTRIBUTES |
FILE_WRITE_EA |
FILE_APPEND_DATA |
SYNCHRONIZE;
internal const long FILE_GENERIC_READ = STANDARD_RIGHTS_READ |
FILE_READ_DATA |
FILE_READ_ATTRIBUTES |
FILE_READ_EA |
SYNCHRONIZE;
internal const int CREATE_NEW = 1;
internal const int CREATE_ALWAYS = 2;
internal const int OPEN_EXISTING = 3;
internal const int MAX_PATH = 260;
internal const int MAX_ALTERNATE = 14;
enum SE_OBJECT_TYPE
{
SE_UNKNOWN_OBJECT_TYPE,
SE_FILE_OBJECT,
SE_SERVICE,
SE_PRINTER,
SE_REGISTRY_KEY,
SE_LMSHARE,
SE_KERNEL_OBJECT,
SE_WINDOW_OBJECT,
SE_DS_OBJECT,
SE_DS_OBJECT_ALL,
SE_PROVIDER_DEFINED_OBJECT,
SE_WMIGUID_OBJECT,
SE_REGISTRY_WOW64_32KEY
}
enum SECURITY_INFORMATION
{
OWNER_SECURITY_INFORMATION = 1,
GROUP_SECURITY_INFORMATION = 2,
DACL_SECURITY_INFORMATION = 4,
SACL_SECURITY_INFORMATION = 8,
}
[DllImport("advapi32.dll", SetLastError = true)]
static extern uint GetSecurityInfo(IntPtr handle, SE_OBJECT_TYPE objectType, SECURITY_INFORMATION securityInfo, out IntPtr sidOwner, out IntPtr sidGroup, out IntPtr dacl, out IntPtr sacl, out IntPtr securityDescriptor);
[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool ConvertSidToStringSid(IntPtr sid, out IntPtr sidString);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern int GetFileAttributesW(string lpFileName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LocalFree(IntPtr handle);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError=true)]
static extern bool CloseHandle(IntPtr handle);
public static string GetFileOwner(string filename)
{
IntPtr fileHandle;
IntPtr ownerSid;
IntPtr groupSid;
IntPtr dacl;
IntPtr sacl;
IntPtr securityDescriptor = IntPtr.Zero;
uint result = 0;
try
{
fileHandle = GetFileHandle(filename);
result = GetSecurityInfo(fileHandle, SE_OBJECT_TYPE.SE_FILE_OBJECT, SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION | SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, out ownerSid, out groupSid, out dacl, out sacl, out securityDescriptor);
IntPtr ptrString = IntPtr.Zero;
bool success = ConvertSidToStringSid(ownerSid, out ptrString);
string strSid = Marshal.PtrToStringAuto(ptrString);
Marshal.FreeHGlobal(ptrString);
// convert the user sid string to a domain\name. Needs .NET 2.0 or better
string account = new SecurityIdentifier(strSid).Translate(typeof(NTAccount)).ToString();
if (!string.IsNullOrEmpty(account))
{
return account; // domain\user
}
else
{
// Console.WriteLine("Could not translate SID to username. User may have been deleted.");
return strSid;
}
}
finally
{
LocalFree(securityDescriptor);
// If you use a SafeFileHandle, do not call CloseHandle as the CLR will close it for you (even if it's already closed).
// CloseHandle(fileHandle);
}
}
public static IntPtr GetFileHandle(string filename)
{
if (filename.Length >= MAX_PATH) filename = AddLongPathPrefix(filename);
SafeFileHandle hfile = CreateFile(filename, (int)FILE_GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
if (hfile.IsInvalid) ThrowWin32Exception();
return hfile.DangerousGetHandle();
}
public static string AddLongPathPrefix(string path)
{
if (path.StartsWith(@"\\?\")) return path;
var newpath = path;
if (newpath.StartsWith("\\"))
{
newpath = @"\\?\UNC\" + newpath.Substring(2);
}
else if (newpath.Contains(":"))
{
newpath = @"\\?\" + newpath;
}
else
{
var currdir = Environment.CurrentDirectory;
newpath = CombinePath(currdir, newpath);
while (newpath.Contains("\\.\\")) newpath = newpath.Replace("\\.\\", "\\");
newpath = @"\\?\" + newpath;
}
return newpath.TrimEnd('.');
}
public static string CombinePath(string path1, string path2)
{
return path1.TrimEnd('\\') + "\\" + path2.TrimStart('\\').TrimEnd('.');
}
public static bool Exists(string path)
{
if (path.Length < MAX_PATH) return System.IO.File.Exists(path);
var attr = GetFileAttributesW(AddLongPathPrefix(path));
return (attr != INVALID_FILE_ATTRIBUTES && (((attr & FILE_ATTRIBUTE_ARCHIVE) == FILE_ATTRIBUTE_ARCHIVE) ||
((attr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)));
}
private static void ThrowWin32Exception()
{
int code = Marshal.GetLastWin32Error();
if (code != 0)
{
throw new System.ComponentModel.Win32Exception(code);
}
}
}
}
"@
}
function Get-FileOwner {
# Returns the owner of a file as string.
# The script can handle both 'normal' path lengths aswell as Long paths (up to approx. 32760 characters)
# The returned string MAY contain names such as "NT AUTHORITY\SYSTEM", "NT AUTHORITY\IUSR" or "BUILTIN\Administrators"
# so you will have to exclude those afterwards if need be. When a string in the form of a SID string like
# "S-1-5-21-3623811015-3361044348-30300820-1013" is returned, it usually means the user has already been deleted.
[CmdletBinding()]
param(
[ValidateNotNullOrEmpty()]
[string]$Path
)
if ([Win32Api.LongFileName]::Exists($Path)) {
return [Win32Api.LongFileName]::GetFileOwner($Path)
}
Write-Warning "Path not found '$Path'"
}
This script was built based on information gathered from multiple places, but especially by adapting the code from Wolf5