Feeds:
Записи
Комментарии

Получение handle Name

Существует большое количество утилит для получения handle Name. Наиболее известные – handle.exe , Process Explorer, Process Hacker. Для PowerShell доступен модуль PoshInternals за авторством Adam Driscoll.

fm

После установки модуля, будет доступен командлет Get-Handle:

gh

Но на Windows 10 x64, командлет выдавал странные результаты. Утилита handle.exe  — отрабатывает штатно.

Ruben Boonen предоставил прекрасный скрипт для получения открытых handle-ов для указанного процесса — Get-Handles.ps1 . Его скрипт был немного изменен, чтобы в вывод не попадала лишняя информация и не было группировки. Для получения имени, используем функцию NtQueryObject .

Add-Type -TypeDefinition @'
    using System;
	using System.Runtime.InteropServices;

    public enum OBJECT_INFORMATION_CLASS
    {
        ObjectBasicInformation,
        ObjectNameInformation,
        ObjectTypeInformation,
        ObjectAllInformation,
        ObjectDataInformation
    }

    [Flags]
    public enum ProcessAccessFlags : uint
    {
        All                     = 0x001F0FFF,
        Terminate               = 0x00000001,
        CreateThread            = 0x00000002,
        VirtualMemoryOperation  = 0x00000008,
        VirtualMemoryRead       = 0x00000010,
        VirtualMemoryWrite      = 0x00000020,
        DuplicateHandle         = 0x00000040,
        CreateProcess           = 0x000000080,
        SetQuota                = 0x00000100,
        SetInformation          = 0x00000200,
        QueryInformation        = 0x00000400,
        QueryLimitedInformation = 0x00001000,
        Synchronize             = 0x00100000
    }

    [Flags]
    public enum DuplicateOptions : uint
    {
        DUPLICATE_CLOSE_SOURCE = 0x00000001,
        DUPLICATE_SAME_ACCESS = 0x00000002
    }

    public struct OBJECT_NAME_INFORMATION
    {
        public UNICODE_STRING Name;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct UNICODE_STRING : IDisposable
    {
        public ushort Length;
        public ushort MaximumLength;
        private IntPtr buffer;

        public UNICODE_STRING(string s)
        {
           Length = (ushort)(s.Length * 2);
           MaximumLength = (ushort)(Length + 2);
           buffer = Marshal.StringToHGlobalUni(s);
        }

        public void Dispose()
        {
            Marshal.FreeHGlobal(buffer);
            buffer = IntPtr.Zero;
        }

        public override string ToString()
        {
            return Marshal.PtrToStringUni(buffer);
        }
    }
    
    public enum NtStatus : uint
    {
        Success = 0x00000000,
        InvalidHandle = 0xc0000008
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SYSTEM_HANDLE_INFORMATION
    {
        public UInt32 ProcessId;
        public Byte ObjectTypeNumber;
        public Byte Flags;
        public UInt16 HandleValue;
        public IntPtr Object_Pointer;
        public UInt32 GrantedAccess;
    }

    public static class Ntdll
	{
        [DllImport("ntdll.dll", SetLastError = true)]
        public static extern NtStatus NtQueryObject(
            [In]  IntPtr Handle,
            [In]  OBJECT_INFORMATION_CLASS ObjectInformationClass,
            [Out] IntPtr ObjectInformation,
            [In]  int ObjectInformationLength,
            [Out] out int ReturnLength);
    
        [DllImport("ntdll.dll")] 
        public static extern int NtQuerySystemInformation( 
            int SystemInformationClass, 
            IntPtr SystemInformation, 
            int SystemInformationLength, 
            ref int ReturnLength);
    }

    public static class Kernel32
    {
        [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool CloseHandle(IntPtr hObject);
        [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, uint processId);
        [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr GetCurrentProcess();
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool DuplicateHandle(IntPtr hSourceProcessHandle,
            IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle,
            uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, DuplicateOptions options);
        [DllImport("kernel32.dll")]
            public static extern uint QueryDosDevice(string lpDeviceName, System.Text.StringBuilder lpTargetPath, uint ucchMax);
    }
'@            
            
function  ConvertTo-RegularFileName             
{            
    param($RawFileName)            
            
    foreach($logicalDrive in [Environment]::GetLogicalDrives())             
    {            
       $targetPath = New-Object System.Text.StringBuilder 256            
       if([Kernel32]::QueryDosDevice($logicalDrive.Substring(0, 2), $targetPath, 256) -eq 0)             
       {            
          return $targetPath              
       }            
       $targetPathString = $targetPath.ToString()            
       if($RawFileName.StartsWith($targetPathString))            
       {            
          $RawFileName = $RawFileName.Replace($targetPathString,$logicalDrive.Substring(0, 2))            
          break            
       }            
    }            
    return $RawFileName            
}            
            
function Get-Handles {            
<#
.SYNOPSIS
	Use NtQuerySystemInformation::SystemHandleInformation to get a list of
	open handles in the specified process, works on x32/x64.

	Notes:
	* For more robust coding I would recomend using @mattifestation's
	  Get-NtSystemInformation.ps1 part of PowerShellArsenal.

.DESCRIPTION
	Author: Ruben Boonen (@FuzzySec)
	License: BSD 3-Clause
	Required Dependencies: None
	Optional Dependencies: None

.EXAMPLE
	C:\PS> Get-Handles -ProcID 1234
#>            
            
 [CmdletBinding()]            
 param (            
  [Parameter(Mandatory = $True)]            
  [int]$ProcID            
 )            
            
 # Make sure the PID exists            
 if (!$(get-process -Id $ProcID -ErrorAction SilentlyContinue)) {            
  Write-Verbose "[!] The specified PID doesn't exist, exiting..`n"            
  Return            
 } else {            
  Write-Verbose "[>] PID $ProcID --> $((Get-Process -Id $ProcID).ProcessName)"            
 }            
            
 # Flag switches (0 = NONE?)            
 $FlagSwitches = @{            
  0 = 'NONE'            
  1 = 'PROTECT_FROM_CLOSE'            
  2 = 'INHERIT'            
 }            
             
 # Taken from @mattifestation --> Get-NtSystemInformation.ps1            
 # https://github.com/mattifestation/PowerShellArsenal/blob/master/WindowsInternals/Get-NtSystemInformation.ps1            
 $OSVersion = [Version](Get-WmiObject Win32_OperatingSystem).Version            
 $OSMajorMinor = "$($OSVersion.Major).$($OSVersion.Minor)"            
 switch ($OSMajorMinor)            
 {            
  '10.0' # Windows 10 - Incomplete still, but 99% of the what you will see in any given process (work in progress, need to pull up KD)            
  {            
   $TypeSwitches = @{            
    0x03 = 'Directory'; 0x04 = 'SymbolicLink'; 0x05 = 'Token'; 0x07 = 'Process'; 0x08 = 'Thread';            
    0x0D = 'Event'; 0x0E = 'Mutant'; 0x10 = 'Semaphore'; 0x11 = 'Timer'; 0x12 = 'IRTimer';            
    0x15 = 'WindowStation'; 0x16 = 'Desktop'; 0x17 = 'Composition'; 0x18 = 'RawInputManager';            
    0x19 = 'TpWorkerFactory'; 0x1E = 'IoCompletion'; 0x1F = 'WaitCompletionPacket'; 0x20 = 'File';            
    0x21 = 'TmTm'; 0x22 = 'TmTx'; 0x23 = 'TmRm'; 0x24 = 'TmEn'; 0x25 = 'Section'; 0x26 = 'Session';            
    0x27 = 'Partition'; 0x28 = 'Key'; 0x29 = 'ALPC Port'; 0x2C = 'EtwRegistration'; 0x2F = 'DmaDomain';            
    0x31 = 'FilterConnectionPort';            
   }            
  }            
              
  '6.2' # Windows 8 and Windows Server 2012            
  {            
   $TypeSwitches = @{            
    0x02 = 'Type'; 0x03 = 'Directory'; 0x04 = 'SymbolicLink'; 0x05 = 'Token'; 0x06 = 'Job';            
    0x07 = 'Process'; 0x08 = 'Thread'; 0x09 = 'UserApcReserve'; 0x0A = 'IoCompletionReserve';            
    0x0B = 'DebugObject'; 0x0C = 'Event'; 0x0D = 'EventPair'; 0x0E = 'Mutant'; 0x0F = 'Callback';            
    0x10 = 'Semaphore'; 0x11 = 'Timer'; 0x12 = 'IRTimer'; 0x13 = 'Profile'; 0x14 = 'KeyedEvent';            
    0x15 = 'WindowStation'; 0x16 = 'Desktop'; 0x17 = 'CompositionSurface'; 0x18 = 'TpWorkerFactory';            
    0x19 = 'Adapter'; 0x1A = 'Controller'; 0x1B = 'Device'; 0x1C = 'Driver'; 0x1D = 'IoCompletion';            
    0x1E = 'WaitCompletionPacket'; 0x1F = 'File'; 0x20 = 'TmTm'; 0x21 = 'TmTx'; 0x22 = 'TmRm';            
    0x23 = 'TmEn'; 0x24 = 'Section'; 0x25 = 'Session'; 0x26 = 'Key'; 0x27 = 'ALPC Port';            
    0x28 = 'PowerRequest'; 0x29 = 'WmiGuid'; 0x2A = 'EtwRegistration'; 0x2B = 'EtwConsumer';            
    0x2C = 'FilterConnectionPort'; 0x2D = 'FilterCommunicationPort'; 0x2E = 'PcwObject';            
    0x2F = 'DxgkSharedResource'; 0x30 = 'DxgkSharedSyncObject';            
   }            
  }            
             
  '6.1' # Windows 7 and Window Server 2008 R2            
  {            
   $TypeSwitches = @{            
    0x02 = 'Type'; 0x03 = 'Directory'; 0x04 = 'SymbolicLink'; 0x05 = 'Token'; 0x06 = 'Job';            
    0x07 = 'Process'; 0x08 = 'Thread'; 0x09 = 'UserApcReserve'; 0x0a = 'IoCompletionReserve';            
    0x0b = 'DebugObject'; 0x0c = 'Event'; 0x0d = 'EventPair'; 0x0e = 'Mutant'; 0x0f = 'Callback';            
    0x10 = 'Semaphore'; 0x11 = 'Timer'; 0x12 = 'Profile'; 0x13 = 'KeyedEvent'; 0x14 = 'WindowStation';            
    0x15 = 'Desktop'; 0x16 = 'TpWorkerFactory'; 0x17 = 'Adapter'; 0x18 = 'Controller';            
    0x19 = 'Device'; 0x1a = 'Driver'; 0x1b = 'IoCompletion'; 0x1c = 'File'; 0x1d = 'TmTm';            
    0x1e = 'TmTx'; 0x1f = 'TmRm'; 0x20 = 'TmEn'; 0x21 = 'Section'; 0x22 = 'Session'; 0x23 = 'Key';            
    0x24 = 'ALPC Port'; 0x25 = 'PowerRequest'; 0x26 = 'WmiGuid'; 0x27 = 'EtwRegistration';            
    0x28 = 'EtwConsumer'; 0x29 = 'FilterConnectionPort'; 0x2a = 'FilterCommunicationPort';            
    0x2b = 'PcwObject';            
   }            
  }            
             
  '6.0' # Windows Vista and Windows Server 2008            
  {            
   $TypeSwitches = @{            
    0x01 = 'Type'; 0x02 = 'Directory'; 0x03 = 'SymbolicLink'; 0x04 = 'Token'; 0x05 = 'Job';            
    0x06 = 'Process'; 0x07 = 'Thread'; 0x08 = 'DebugObject'; 0x09 = 'Event'; 0x0a = 'EventPair';            
    0x0b = 'Mutant'; 0x0c = 'Callback'; 0x0d = 'Semaphore'; 0x0e = 'Timer'; 0x0f = 'Profile';            
    0x10 = 'KeyedEvent'; 0x11 = 'WindowStation'; 0x12 = 'Desktop'; 0x13 = 'TpWorkerFactory';            
    0x14 = 'Adapter'; 0x15 = 'Controller'; 0x16 = 'Device'; 0x17 = 'Driver'; 0x18 = 'IoCompletion';            
    0x19 = 'File'; 0x1a = 'TmTm'; 0x1b = 'TmTx'; 0x1c = 'TmRm'; 0x1d = 'TmEn'; 0x1e = 'Section';            
    0x1f = 'Session'; 0x20 = 'Key'; 0x21 = 'ALPC Port'; 0x22 = 'WmiGuid'; 0x23 = 'EtwRegistration';            
    0x24 = 'FilterConnectionPort'; 0x25 = 'FilterCommunicationPort';            
   }            
  }            
 }            
             
 Write-Verbose "[+] Calling NtQuerySystemInformation::SystemHandleInformation"            
 [int]$BuffPtr_Size = 0            
 while ($true) {            
  [IntPtr]$BuffPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($BuffPtr_Size)            
  $SystemInformationLength = New-Object Int            
             
  $CallResult = [Ntdll]::NtQuerySystemInformation(16, $BuffPtr, $BuffPtr_Size, [ref]$SystemInformationLength)            
              
  # STATUS_INFO_LENGTH_MISMATCH            
  if ($CallResult -eq 0xC0000004) {            
   [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)            
   [int]$BuffPtr_Size = [System.Math]::Max($BuffPtr_Size,$SystemInformationLength)            
  }            
  # STATUS_SUCCESS            
  elseif ($CallResult -eq 0x00000000) {            
   Write-Verbose "[?] Success, allocated $BuffPtr_Size byte result buffer`n"            
   break            
  }            
  # Probably: 0xC0000005 -> STATUS_ACCESS_VIOLATION            
  else {            
   [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)            
   Write-Verbose "[!] Error, NTSTATUS Value: $('{0:X}' -f ($CallResult))`n"            
   return            
  }            
 }            
             
 $SYSTEM_HANDLE_INFORMATION = New-Object SYSTEM_HANDLE_INFORMATION            
 $SYSTEM_HANDLE_INFORMATION = $SYSTEM_HANDLE_INFORMATION.GetType()            
 if ([System.IntPtr]::Size -eq 4) {            
  $SYSTEM_HANDLE_INFORMATION_Size = 16 # This makes sense!            
 } else {            
  $SYSTEM_HANDLE_INFORMATION_Size = 24 # This doesn't make sense, should be 20 on x64 but that doesn't work.            
                                             # Ask no questions, hear no lies!            
 }            
             
 $BuffOffset = $BuffPtr.ToInt64()            
 $HandleCount = [System.Runtime.InteropServices.Marshal]::ReadInt32($BuffOffset)            
 $BuffOffset = $BuffOffset + [System.IntPtr]::Size            
 Write-Verbose "[>] Result buffer contains $HandleCount SystemHandleInformation objects"            
             
 $SystemHandleArray = @()            
 for ($i=0; $i -lt $HandleCount; $i++){            
  # PtrToStructure only objects we are targeting, this is expensive computation            
  if ([System.Runtime.InteropServices.Marshal]::ReadInt32($BuffOffset) -eq $ProcID) {            
   $SystemPointer = New-Object System.Intptr -ArgumentList $BuffOffset            
   $Cast = [system.runtime.interopservices.marshal]::PtrToStructure($SystemPointer,[type]$SYSTEM_HANDLE_INFORMATION)            
               
   $HashTable = @{            
    PID = $Cast.ProcessID            
    ObjectType = if (!$($TypeSwitches[[int]$Cast.ObjectTypeNumber])) { "0x$('{0:X2}' -f [int]$Cast.ObjectTypeNumber)" } else { $TypeSwitches[[int]$Cast.ObjectTypeNumber] }            
    HandleFlags = $FlagSwitches[[int]$Cast.Flags]            
    Handle = "0x$('{0:X4}' -f [int]$Cast.HandleValue)"            
    KernelPointer = if ([System.IntPtr]::Size -eq 4) { "0x$('{0:X}' -f $Cast.Object_Pointer.ToInt32())" } else { "0x$('{0:X}' -f $Cast.Object_Pointer.ToInt64())" }            
    AccessMask = "0x$('{0:X8}' -f $($Cast.GrantedAccess -band 0xFFFF0000))"            
   }            
               
   $Object = New-Object PSObject -Property $HashTable            
   $SystemHandleArray += $Object            
               
  }            
            
  $BuffOffset = $BuffOffset + $SYSTEM_HANDLE_INFORMATION_Size            
 }            
             
 Write-Verbose "[>] PID $ProcID has $($SystemHandleArray.count) handle objects"            
 if ($($SystemHandleArray.count) -eq 0) {            
  [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)            
  Write-Verbose "[!] No process handles found, exiting..`n"            
  Return            
 }            
             
 # Set column order and auto size            
 $SystemHandleArray | Select-Object PID,ObjectType,HandleFlags,Handle,KernelPointer,AccessMask            
             
 # Free SYSTEM_HANDLE_INFORMATION array            
 [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)            
}            
            
Function Get-HandleName {            
    [CmdLetBinding()]            
    param(            
        [Parameter(Mandatory=$true,            
            ValueFromPipelineByPropertyName=$true)]            
            [IntPtr][Int]$Handle,            
        [Parameter(Mandatory=$true,            
            ValueFromPipelineByPropertyName=$true)]            
        [Alias("PID")]            
            [UInt32]$ID,            
        [Parameter(Mandatory=$false,            
            ValueFromPipelineByPropertyName=$true)]            
        [Alias("ObjectType")]            
            [String]$Type            
    )            
            
    Process            
    {            
        $duplicatedHandle = [IntPtr]::Zero            
            
        $processHandle = [Kernel32]::OpenProcess([ProcessAccessFlags]::DuplicateHandle, $true, $ID)            
        $process = [Kernel32]::GetCurrentProcess()            
        $options = [DuplicateOptions]::DUPLICATE_SAME_ACCESS            
        [bool]$success = [Kernel32]::DuplicateHandle($processHandle, $handle, $process, [ref]$duplicatedHandle, 0, $false, $options)            
        [Kernel32]::CloseHandle($processHandle) | Out-Null            
                   
        if(!$success)             
        {            
            return            
        }            
            
        $dummy = 0            
        $length = [Runtime.InteropServices.Marshal]::SizeOf([type][OBJECT_NAME_INFORMATION]) + 256            
        [IntPtr]$buffer = [Runtime.InteropServices.Marshal]::AllocHGlobal($length)            
                
        $status = [NTDLL]::NtQueryObject(            
            $duplicatedHandle,            
            [OBJECT_INFORMATION_CLASS]::ObjectNameInformation,             
            $buffer,             
            $length,             
            [ref]$dummy            
        )            
            
        if ($status -eq [NtStatus]::Success)            
        {            
            $temp = [Runtime.InteropServices.Marshal]::PtrToStructure($buffer, [type][OBJECT_NAME_INFORMATION])            
            $rName = $temp.Name.ToString()            
            
            if($rName)            
            {            
                $Name = ConvertTo-RegularFileName $rName            
                [PSCustomObject]@{            
                    Handle = "0x{0:X}" -f [long]$Handle            
                    ID     = $ID            
                    Type   = $Type            
                    Name   = $Name            
                }                        
            }            
        }            
                
        [Runtime.InteropServices.Marshal]::FreeHGlobal($buffer) | Out-Null            
        [Kernel32]::CloseHandle($duplicatedHandle) | Out-Null            
     }            
}            

Пример вывода:

Get-Handles -ProcID (Get-Process FoxitReader).ID -Verbose | Get-HandleName

gh1

$h = Get-Handles -ProcID (Get-Process FoxitReader).ID | Get-HandleName            
$h | Where Name -match "pdf"

gh2

 

Get-HandleName.ps1https://yadi.sk/d/FoEzdYQd3Bd2vu

В серверных операционных системах Microsoft по умолчанию отсутствует служба WebClient. Данный функционал включается установкой дополнительного компонента, который зависит от версии OS.

Windows Server 2008 R2*,2012,2012 R2 (компонент Desktop-Experience)**:

  Install-WindowsFeature Desktop-Experience

* Для Windows Server 2008 R2 командлет   Install-WindowsFeature отсутствует, вместо него используйте Add-WindowsFeature .

de211116

Windows Server 2016(компонент WebDAV-Redirector)**:

Install-WindowsFeature WebDAV-Redirector –Restart
wd211116

** Требуется обязательная перезагрузка

В систему добавляется две новые службы(WebClient&MRxDAV):

s211116 

Настройка службы WebClient производится через реестр. Подробнее про параметры, можно прочитать —  WebDAV Redirector Registry Settings

Основной параметр BasicAuthLevel имеет значение:

  • 0 — Basic authentication disabled
  • 1 — Basic authentication enabled for SSL shares only
  • 2 or greater — Basic authentication enabled for SSL shares and for non-SSL shares

В более младших версиях, параметр по умолчанию: 2

reg211116

 

Для проверки работы:

nd211116

 

Рассмотрим, как изменить расширенное свойство файлов на примере Comments.

ep10112016

Для получения свойств, можно воспользоваться Com-классом Shell.Application. Подробнее — Retrieving Extended File Properties

# Отобразить расширенные свойства            
$folder = "С:\files"            
$file = "Document"            
$shell = New-Object -ComObject Shell.Application            
$ns = $shell.NameSpace($folder)            
$fn = $ns.Items()| Where {$_.Name -eq $file}            
            
0..60 | Select-Object @{n="Name";e={$ns.GetDetailsOf($ns,$_)}},            
    @{n="Value";e={$ns.GetDetailsOf($fn,$_)}}

Выведем отдельное свойство Comments:

sp101116

Для изменения расширенных свойств Shell.Application не подходит, воспользуемся другими методами.

1.  Office.Application

Разберем на примере Word.Application для других классов отличия лишь в имени класса.

Param(            
 $Path  = "D:\mydoc.docx",            
 $PropertyName  = "Comments",            
 $Value = "$(Get-Date): $env:COMPUTERNAME - $env:USERNAME",            
 [switch]$Show            
)            
            
# Создаем экземпляр класса            
$App = New-Object -ComObject word.application            
$App.Visible = $false            
$Document = $App.Documents.Open($Path)            
$BuiltIn = $Document.BuiltInDocumentProperties             
            
# Список свойств            
$Property = @(             
 @{n="Name";e={$_.GetType().InvokeMember("Name", "GetProperty",$null,$_,$null)}},             
 @{n="Value";e={$_.GetType().InvokeMember("Value", "GetProperty",$null,$_,$null)}}            
)            
            
if($Show)            
{            
 $BuiltIn | Select-Object -Property $Property | Out-Default            
}            
else             
{            
 # Свойство            
 $PP = $BuiltIn.GetType().InvokeMember("Item", "GetProperty",$null,$BuiltIn,$PropertyName)              
 $PP | Select-Object -Property $Property | Out-Default            
            
 # Изменим свойство            
 $PP.GetType().InvokeMember("Value", "SetProperty",$null,$PP,$Value)            
            
 $PP = $BuiltIn.GetType().InvokeMember("Item", "GetProperty",$null,$BuiltIn,$PropertyName)              
 $PP | Select-Object -Property $Property | Out-Default            
             
    # Сохраняем            
 $Document.Saved = $false            
 $Document.Save()            
}            
            
# Освобождаем ресурсы            
$App.Quit()            
[Runtime.InteropServices.Marshal]::ReleaseComObject($App) | Out-Null            
$App = $null            
[GC]::Collect()            
[GC]::WaitForPendingFinalizers()

Для получения свойств, параметр Show:

wep101116

Для изменения Comments:

wcc101116

2. Библиотека Dsofile.dll

Скачаем и установим библиотеку – https://www.microsoft.com/en-us/download/confirmation.aspx?id=8422.

Зарегистрируем библиотеку:

regsvr32 "C:\Program Files (x86)\DsoFile\dsofile.dll"

reg101116

Если система у Вас x-64, то запустите x-86 версию PowerShell.

Add-Type -Path "C:\Program Files (x86)\DsoFile\Demo\Interop.Dsofile.dll"            
$file = "D:\mydoc.docx"            
$op = New-Object DsoFile.OleDocumentPropertiesClass            
# Режим редактирования            
$op.Open($file,$false)            
            
# Получение свойств            
$op.SummaryProperties            
            
# Комментарий            
$op.SummaryProperties.Comments            
            
# Изменим комментарий            
$op.SummaryProperties.Comments = Get-Date            
            
# Сохраняем            
$op.Save()            

3.  OpenXmlSDK

Скачиваем и устанавливаем — https://www.microsoft.com/en-us/download/details.aspx?id=30425

Add-Type -Path "C:\Program Files (x86)\Open XML SDK\V2.5\lib\DocumentFormat.OpenXml.dll"            
$file = "D:\mydoc.docx"            
$file = [DocumentFormat.OpenXml.Packaging.WordprocessingDocument]::Open($file,$true)            
$file.PackageProperties.Description = Get-Date            
$file.Close()            

4. WindowsAPICodePack

Для работы с PackageManagement есть хорошая документация с подробными примерами — https://msdn.microsoft.com/ru-ru/powershell/wmf/5.0/oneget_cmdlets

# Найдем пакеты для установки            
$package = "WindowsAPICodePack-Shell","WindowsAPICodePack-Core"            
Find-Package -Name $package -Provider NuGet -Source http://www.nuget.org/api/v2            
            
pk101116
# Установим            
$package = "WindowsAPICodePack-Shell","WindowsAPICodePack-Core"            
Find-Package -Name $package -Provider NuGet -Source http://www.nuget.org/api/v2 | Install-Package            
            
# Проверим установку            
Get-Package $package | Format-List Name,Source            

gp101116

Внесем изменения в Comments:
Add-Type -Path "C:\Program Files\NuGet\Packages\WindowsAPICodePack-Core.1.1.2\lib\Microsoft.WindowsAPICodePack.dll"            
Add-Type -Path "C:\Program Files\NuGet\Packages\WindowsAPICodePack-Shell.1.1.1\lib\Microsoft.WindowsAPICodePack.Shell.dll"            
            
$file = "D:\mydoc.docx"            
$sh = [Microsoft.WindowsAPICodePack.Shell.ShellFile]::FromFilePath($file)                       
$sh.Properties.System.Comment.Value = Get-Date

Задача: Получить адрес клиента, который подключается через VPN к RRAS серверу с помощью PowerShell.

В нашей лабе:

  1. SRV-DC01  — Контроллер домена (ОС 2012+) с роль шлюза Windows PowerShell Web Access
  2. SRV-VPN    — Сервер с ролью RRAS (ОС 2012+)
  3. W10-CL1   — Клиент (ОС 2012+)

Пример схемы:

Windows PowerShell Web Access diagram

1. Установка  Windows PowerShell Web Access

Данная процедура подробно описана в Install and Use Windows PowerShell Web Access ,

поэтому лишь выполним основные действия на SRV-DC01.

# Установка роли            
Install-WindowsFeature –Name WindowsPowerShellWebAccess -IncludeManagementTools -Restart            
            
# После перезагрузки            
# Установка и настройка web приложения с self-signed сертификатом(            
# рекомендуется использовать только в тестовых средах)            
# Path: /pswa            
# ApplicationPool: pswa_pool            
# EnabledProtocols: http            
# PhysicalPath: %windir%/Web/PowerShellWebAccess/wwwroot            
            
Install-PswaWebApplicationUseTestCertificate
ipswa08112016

Осталось добавить только правила доступа для PSWA. Лучше создавать правила для групп, а не отдельных пользователей.

Для нашего примера, создадим группу helpdesk_vpn в которую будет входить пользователь contoso\user1.

# Создадим группу            
New-ADGroup -Name helpdesk_vpn -GroupCategory Security -GroupScope Universal            
            
# Добавим пользователя в нее            
Add-ADGroupMember -Identity helpdesk_vpn -Members User1

После создания группы, создадим правило для доступа с параметрами: подключение ведется к SRV-VPN, кому разрешено contoso\helpdesk, constrained endpoints с именем VPN.

# Правило доступа                      
$param = @{            
    RuleName              = "Helpdesk VPN"             
    UserGroupName    = "contoso\helpdesk_vpn"             
    ComputerName      =  "srv-vpn"            
    ConfigurationName = "VPN"            
    Force                     = $true            
}            
            
Add-PswaAuthorizationRule @param            

PS > Add-PswaAuthorizationRule @param

Id    RuleName         User

—    ———         —-

0     Helpdesk VPN     contoso\helpdesk_vpn

2. Перейдем к созданию constrained endpoints на сервере SRV-VPN

Требуемый функция Get-RemoteAccessConnectionStatistics находится в модуле RemoteAccess.

В этом можно убедиться:

grac08112016 

Т.к. у helpdesk_vpn права на выполнения данного функции — нет, то нужно  создать отдельный аккаунт от которого будет запускаться функция или добавить пользователя в группу у которой есть право на выполнение(например Администраторы). Начиная с PowerShell v5+  у командлета

New-PSSessionConfigurationFile появился параметр  -RunAsVirtualAccount , позволяя обойтись без создания дополнительных групп и пользователей. Т.к. данный аккаунт по умолчанию входит в локальную группу Администраторов в ОС начиная с 2008 R2. При конфигурировании надо быть очень осторожным, т.к. неверное конфигурирования даст пользователю возможность запускать команды от более привилегированного пользователя.

PSv5+: 
# Создание constrained endpoints            
# Данные командлеты требуются, если пользовать захочит сделать Enter-PsSession            
$Cmdlets = @(            
    'Get-Command', 'Out-Default',            
    'Exit-PSSession', 'Measure-Object',            
    'Select-Object' , 'Get-FormatData'            
)            
 
# Параметры                        
$param = @{                        
    Path                          = ".\VisibleCmdlets.pssc"                        
    VisibleCmdlets          = $Cmdlets                        
    VisibleFunctions        = "RemoteAccess\Get-RemoteAccessConnectionStatistics"            
    SessionType              = "RestrictedRemoteServer"                           
    LanguageMode          = "NoLanguage"                        
    RunAsVirtualAccount = $true                                   
}

# Конфигурирование и назначение прав
New-PSSessionConfigurationFile  @param
Register-PSSessionConfiguration -Path .\VisibleCmdlets.pssc -Name VPN -Force
Get-PSSessionConfiguration VPN | Set-PSSessionConfiguration -ShowSecurityDescriptorUI

Для PSv4+:

Тут указываем учетную запись обладающую правами локального администратора

(отвечает параметр -RunAsCredential «Contoso\Administrator») .

# Создание constrained endpoints            
# Данные командлеты требуются, если пользовать захочит сделать Enter-PsSession            
$Cmdlets = @(            
    'Get-Command', 'Out-Default',            
    'Exit-PSSession', 'Measure-Object',            
    'Select-Object' , 'Get-FormatData'            
)            
            
# Параметры            
$param = @{            
    Path = ".\VisibleCmdlets.pssc"            
    VisibleCmdlets = $Cmdlets            
    VisibleFunctions = "Get-RemoteAccessConnectionStatistics"            
    ModulesToImport = "RemoteAccess"            
}            
            
# Конфигурирование и назначение права            
New-PSSessionConfigurationFile  @param            
Register-PSSessionConfiguration -Path .\VisibleCmdlets.pssc -Name VPN -RunAsCredential "Contoso\Administrator" -Force            
Get-PSSessionConfiguration VPN | Set-PSSessionConfiguration -ShowSecurityDescriptorUI            

perm08112016

Если все прошло без ошибок, то у Вас должно быть примерно следущее:

gpc08112016

3. Осталось сделать тест и убедиться, что все отрабатывает хорошо.

Переходим по адресу https://mypswa.ru/pswa и заполняем поля

User Name, Password, Computer Name,Configuration name и нажимаем Sign In.

pswa08112016

Выполняем функцию:

pswac08112016

Доступные нам команды в сессии:

comm08112016

 

По умолчанию PowerShell обладает довольно слабой поддержкой  командлетов для гибкого управления правами NTFS. Зачастую Get-Acl/Set-Acl не справляются ,даже, с базовыми задачами. При использовании .Net  / WMI (Win32_Trustee, Win32_ACE, Win32_SecurityDescriptor) API позволяет расширить функционал при этом не добавляя гибкости и простоты, с значительным увеличением кодовой базы. Поставляемые по умолчанию  утилиты cacls/icacls/takeown –  справляются со своими задачами, но имеют ограничения и сложность в добавлении расширенного функционала. 

Сторонние утилиты обладающие более широким функционалом, наиболее известные:

SubInACL — https://www.microsoft.com/en-us/download/details.aspx?id=23510

SetACL(для скриптования есть поддержка COM-интерфейса) — https://helgeklein.com

На мой взгляд более удобная  — это SetACL, обладает отличным функционалом, хорошей документаций с большим обилием примеров, поддержка длинных путей 256+ и распространяется бесплатно.

Raimund Andrée написал прекрасный модуль NTFSSecurity для работы с NTFS, обладающий отличным функционал и легкостью управления,поддержка длинных путей 256+. Кто хочет более детально изучить работу модуля может ознакомится с исходными кодами проекта на C# — https://github.com/raandree/NTFSSecurity.

В качестве документации, настоятельно рекомендую прочитать:

NTFSSecurity Tutorial 1 — Getting, adding and removing permissions

NTFSSecurity Tutorial 2 — Managing NTFS Inheritance and Using Privileges

В нашем примере версия PowerShell 5.0 (или выше 5.1 в состоянии preview, 6.0 alpha) , который имеет поддержку Windows 7+.  Модуль NTFSSecurity  не поддерживает использование Linux/MacOS.

Установка модуля.

В PowerShell V3 была добавлена новая переменная среды — $env:PSModulePath.

Переменная среды PSModulePath содержит пути откуда можно импортировать модули, когда не указан полный путь к модулю. 

Дополнительные сведения:

Modifying the PSModulePath Installation Path — https://msdn.microsoft.com/en-us/library/dd878326(v=vs.85).aspx

Installing a PowerShell Module — https://msdn.microsoft.com/en-us/library/dd878350(v=vs.85).aspx

При изменении переменной PSModulePath , чтобы настройки вступили в силу, требуется отправить сообщение WM_SETTINGCHANGE с параметром Environment. Пример скрипта для отправки сообщения — Invoke-WMSettingsChange by Oisin Grehan

В PowerShell V4 PSModulePath  переменная содержит путь:

%ProgramFiles%\WindowsPowerShell\Modules

В моем случае:

PS > $env:PSModulePath.split(";")
C:\Users\Administrator\Documents\WindowsPowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules

В PowerShell v5 добавили замечательную возможность PowerShellGet(V3,V4 – данный модуль тоже доступен) для работы с онлайн репозитариями. Подробнее можно ознакомиться в документации — https://msdn.microsoft.com/en-us/powershell/gallery/readme

И выполняем команду:

Install-Module -Name NTFSSecurity -Scope AllUsers -Verbose -Force            

Где область AllUsers:

The AllUsers scope lets modules be installed in a location that is accessible to all users of the computer, that is

, %systemdrive%:\ProgramFiles\WindowsPowerShell\Modules.

Модуль содержит на момент написания 36 командлетов. Список можно посмотреть:

Get-Command -Module NTFSSecurity

Убедимся, что модуль находится в нужном месте:

PS > Get-Module NTFSSecurity | Format-List            
            
Name              : NTFSSecurity            
Path              : C:\Program Files\WindowsPowerShell\Modules\NTFSSecurity\4.2.3\NTFSSecurity.psm1            
Description       : Windows PowerShell Module for managing file and folder security on NTFS volumes


Использование привилегий

Зачастую разрешения даны только определенным группам пользователей. В редких случаях, некорректного назначения прав deny, удаления групп/пользователей являющихся владельцем объекта. Тем самым,лишая возможности, обладая даже полными правами Администратора системы, чтения ACL-объекта, получая -  access is denied. Администратор по умолчанию обладает привилегией Take ownership of files or other objects (SeTakeOwnershipPrivilege) , которая позволяет стать владельцем любых объектов. Но в этом случае, затираются существующие ACL объекта, что может привести к нежелательному результату и дополнительной работе для администратора.

В нашем примере, отметим только те привилегии, которые имеют отношения к файлам и папкам.

  • Back up files and directories:

Это право пользователя определяет, какие пользователи могут игнорировать разрешения  файлов и каталогов, реестра и другие  в целях резервного копирования системы.

Это право пользователя эквивалентно предоставлению следующих разрешений пользователя или группы, выбранных для всех файлов и папок в системе:

  • Traverse Folder/Execute File

  • List Folder/Read Data

  • Read Attributes

  • Read Extended Attributes

  • Read Permissions

По умолчанию: Administrators и Backup Operators

  • Restore files and directories:

Этот параметр безопасности определяет, какие пользователи могут игнорировать разрешения файлов, каталогов, реестра и другие  при их восстановления из резервной копии и определяет, какие пользователи могут устанавливать владельца для объекта.

Предоставление этого права пользователя учетной записи похож на предоставление учетной записи следующие разрешения для всех файлов и папок в системе:

  • Traverse Folder/Execute File
  • Write

По умолчанию: Administrators и Backup Operators

  • Take ownership of files or other objects:

Разрешает пользователю становиться вла­дельцем системных объектов, в том числе объектов Active Directory, файлов и папок, принтеров, разделов реестра, процессов и потоков.

По умолчанию: Administrators

Для просмотра текущих привилегий, в модуле есть командлет Get-Privileges или можно воспользоваться встроенной утилитой whoami /priv .

pr241016

Создадим простую структуру:
 
md E:\Doc | Out-Null            
Get-Process > E:\Doc\file1.txt            
Get-Service > E:\Doc\file2.txt            
 
Для получения разрешений командлет Get-NTFSAccess ,для получения владельца - Get-NTFSOwner.
 
dacl241016
 

Пример 1:

Уберем все права  и  назначим ‘NT SERVICE\TrustedInstaller’ владельцем.

Set-NTFSOwner E:\DOC -Account 'NT SERVICE\TrustedInstaller'            
Disable-NTFSAccessInheritance E:\DOC -RemoveInheritedAccessRules

Проверяем стандартными средствами:

acg241016

Оба способа возвращают ‘Access is denied’ под учетной записью администратора.

Если нам требуется только посмотреть список файлов, то можно использовать модуль PowerForensics.

Install-Module PowerForensics -Scope AllUsers -Force

Список файлов E:\Doc:

fm241016

Добавим пользователю, скажем Alexander, права на чтение папки без изменения владельца:

adaa241016

Проверим под пользователем:

ua241016

Пример 2:

С "осиротевшими" объектами. Структура папок из первого примера. Добавляем группу и пользователя, назначаем права, а потом удаляем.

ex2241016

Теперь у нас и в ACL, и владелец – только SID. Get-NTFSOrphanedAccess – позволяет получить список прав, только с "осиротевшими" объектами.

or241016

Т.к. структуру файлов мы знаем, позволим Alexander читать только файл file1.txt

fa241016

Get-NTFSOrphanedAccess  — выведет пусто, в отличие от Get-NTFSAccess

alco241016

PS. Для тех у кого часто возникает задача работа с разрешениями NTFS, данный модуль будет отличным дополнением в арсенал утилит и скриптов. Советую, ознакомиться с модулями из заметки, возможно  однажды, они решат вашу задачу немного проще. Да и для общего развития будет неплохой информацией.

  1. NTFSSecurity
  2. UserRights
  3. PowerForensics

Для моделей ZyXEL, которые не поддерживаю управление через протокол SSH , воспользуемся протоколом telnet( включен по умолчанию). Стандартная утилита telnet ,которая поставляется в комплекте (начиная с Windows Vista — включается через установку компонентов Windows *), сложно поддается автоматизации и многочисленные примеры ,связанны с отправкой команд, например с SendKeyshttp://www.maxtblog.com/2012/06/telnet-automation-with-powershell-made-simple/ .

*dism /online /Enable-Feature /FeatureName:TelnetClient

* — Install-WindowsFeature «Telnet-Client»

 

Для автоматизации воспользуемся библиотекой Telnet C# —  https://telnetcsharp.codeplex.com/ . Удобная и простая в управлении. Для работы с библиотекой, ее требуется скомпилировать или скопировать из папки ..\telnet\bin\Release\Telnet.dll.

Param(            
 $HostName     = "192.168.0.1",            
 $Port      = 23,            
 $TimeoutSeconds   = 10,            
 $VirtualScreenWidth  = 80,            
 $VirtualScreenHeight  = 40,            
 $UserName     = "admin",            
 $PassWord     = "admin",            
 $Command     = "show version"            
)            
            
Add-Type -Path "C:\Scripts\Telnet.dll"             
            
$tn = New-Object Telnet.Terminal (            
 $HostName,            
 $Port,             
 $TimeoutSeconds,             
 $VirtualScreenWidth,             
 $VirtualScreenHeight            
)            
            
if($tn.Connect())            
{            
 # Логин            
 $f = $tn.WaitForString("Login")            
 if (!$f) {            
        throw "No login possible"            
 }            
                
 $tn.SendResponse($UserName, $true) | Out-Null            
             
 # Пароль            
    $f = $tn.WaitForString("Password")            
    if (!$f) {            
        throw "No password prompt found"            
 }            
                
    $tn.SendResponse($PassWord, $true) | Out-Null            
                
 # Shell            
 $Command | Foreach-Object {            
  $f = $tn.WaitForString(">");            
  if (!$f) {            
   throw "No > prompt found"            
  }            
            
  # Команда            
  $tn.SendResponse($_, $true) | Out-Null            
  $tn.WaitForChangedScreen() | Out-Null            
    }            
            
    $tn.VirtualScreen.Hardcopy().TrimEnd()            
             
 # LogOut            
 $tn.SendLogout() | Out-Null            
             
 # Закрыть соединение            
 $tn.Close() | Out-Null            
}        
   
tn           

-Command «show ?»

stn

 

-Command «?»

htn

 

Файл: GetRealse.ps1

В проекте PSCX есть замечательная функция Show-Tree , которая послужит  основой в решении задачи.

function Show-ADTreeOU            
{            
    [CmdletBinding()]            
    param(            
        [Parameter(Position=0,             
                   ValueFromPipeline=$true,             
                   ValueFromPipelineByPropertyName=$true)]            
        [ValidateNotNullOrEmpty()]            
        [alias("DistinguishedName")]            
        [string]            
        $Path = (Get-ADDomain).DistinguishedName,            
                                
        [ValidateRange(0, 256)]            
        [int]            
        $Depth = [int]::MaxValue,             
                    
        [Parameter()]            
        [ValidateRange(1, 100)]            
        [int]            
        $IndentSize = 3,             
                    
        [Parameter()]            
        [ValidateRange(0, 2147483647)]            
        [int]            
        $Width,            
                    
        [Parameter()]            
        [switch]            
        $ShowLeafObject,            
            
                    
        [Parameter()]            
        [switch]            
        $UseAsciiLineArt            
    )            
            
    Begin            
    {            
        Set-StrictMode -Version Latest            
                    
                    
        if ($Width -eq 0)            
        {            
            $Width = $host.UI.RawUI.BufferSize.Width            
        }            
                    
        $asciiChars = @{            
            EndCap        = '\'            
            Junction      = '|'            
            HorizontalBar = '-'            
            VerticalBar   = '|'            
        }            
                    
        $cp437Chars = @{            
            EndCap        = '└'            
            Junction      = '├'            
            HorizontalBar = '─'            
            VerticalBar   = '│'            
        }            
                    
        if (($Host.CurrentCulture.TextInfo.OEMCodePage -eq 437) -and !$UseAsciiLineArt)            
        {            
            $lineChars = $cp437Chars            
        }            
        else            
        {            
            $lineChars = $asciiChars            
        }            
                           
        function GetIndentString([bool[]]$IsLast)            
        {            
            $str = ''            
            for ($i=0; $i -lt $IsLast.Count - 1; $i++)            
            {            
                $str += if ($IsLast[$i]) {' '} else {$lineChars.VerticalBar}            
                $str += " " * ($IndentSize - 1)            
            }            
            $str += if ($IsLast[-1]) {$lineChars.EndCap} else {$lineChars.Junction}            
            $str += $lineChars.HorizontalBar * ($IndentSize - 1)            
            $str            
        }            
                    
        function CompactString([string]$String, [int]$MaxWidth = $Width)            
        {            
            $updatedString = $String            
            if ($String.Length -ge $MaxWidth)            
            {            
                $ellipsis = '...'            
                $updatedString = $String.Substring(0, $MaxWidth - $ellipsis.Length - 1) + $ellipsis            
            }            
            $updatedString                
        }            
            
        function ShowItemText([string]$ItemPath, [string]$ItemName, [bool[]]$IsLast)            
        {            
            if ($IsLast.Count -eq 0)             
            {            
                $String = CompactString $ItemPath             
            }            
            else            
            {                     
                $String = CompactString "$(GetIndentString $IsLast)$ItemName "            
            }            
                        
            if($ShowLeafObject)             
            {            
                if($ItemPath)             
                {            
                    $Users = $Script:ADO[$ItemPath].Where({$_.ObjectClass -eq "user"}).Count            
     $Computers = $Script:ADO[$ItemPath].Where({$_.ObjectClass -eq "computer"}).Count            
                    $GR = $Script:ADO[$ItemPath].Where({$_.ObjectClass -eq "group"})            
                    $DL_GR_SEC = $GR.Where({$_.GroupScope -eq "DomainLocal" -and $_.GroupCategory -eq "Security"}).Count            
                    $GL_GR_SEC = $GR.Where({$_.GroupScope -eq "Global" -and $_.GroupCategory -eq "Security"}).Count            
                    $UN_GR_SEC = $GR.Where({$_.GroupScope -eq "Universal" -and $_.GroupCategory -eq "Security"}).Count            
                    $DL_GR_DIS = $GR.Where({$_.GroupScope -eq "DomainLocal" -and $_.GroupCategory -eq "Distribution"}).Count            
                    $GL_GR_DIS = $GR.Where({$_.GroupScope -eq "Global" -and $_.GroupCategory -eq "Distribution"}).Count            
                    $UN_GR_DIS = $GR.Where({$_.GroupScope -eq "Universal" -and $_.GroupCategory -eq "Distribution"}).Count            
            
                    [PSCustomObject]@{            
                        Name      = $String              
                        Users     = $Users            
      Computers = $Computers            
                        DL_GR_SEC = $DL_GR_SEC            
                        GL_GR_SEC = $GL_GR_SEC            
                        UN_GR_SEC = $UN_GR_SEC            
                        DL_GR_DIS = $DL_GR_DIS            
                        GL_GR_DIS = $GL_GR_DIS            
                        UN_GR_DIS = $UN_GR_DIS            
                    }            
                }            
            }             
            else             
            {            
               $String              
            }             
        }            
                    
                    
        function ShowItem([string]$ItemPath, [string]$ItemName='', [bool[]]$IsLast=@())            
        {                    
            # Show current item            
            ShowItemText $ItemPath $ItemName $IsLast            
                        
            # grab its children.  This let's us know if there            
            $childItems = @()            
            if ($IsLast.Count -lt $Depth)            
            {            
                $childItems = @(Get-ADOrganizationalUnit -Filter * -SearchBase $ItemPath -SearchScope OneLevel | Select DistinguishedName, Name)                   
            }            
            
            # Track parent's "last item" status to determine which level gets a vertical bar            
            $IsLast += @($false)            
                                    
            # Recurse through child items            
            for ($i=0; $i -lt $childItems.Count; $i++)            
            {            
                $childItemPath = $childItems[$i].DistinguishedName            
                $childItemName = $childItems[$i].Name            
                $IsLast[-1] = ($i -eq $childItems.Count - 1)            
                ShowItem $childItemPath $childItemName $IsLast            
            }            
        }            
    }            
            
    Process            
    {               
  if($ShowLeafObject)            
  {            
   $ADObjects = @()            
   $ADObjects += Get-ADObject -Filter "ObjectClass -eq 'user' -or ObjectClass -eq 'computers'" -SearchBase $Path            
   $ADObjects += Get-ADGroup -Filter * -SearchBase $Path            
               
   if($ADObjects)            
   {            
    $Script:ADO  = @{}            
    $ADO[$Path] = $ADObjects            
    $ADObjects | Group-Object {$_.DistinguishedName.Split(",",2)[1]} | Foreach-Object {            
     if($_.Name.StartsWith("OU="))            
     {            
      $ADO[$_.Name] = $_.Group            
     }                 
    }            
   }            
  }            
                    
  if($Path)             
  {                  
            ShowItem $Path             
        }             
    }                    
}

shtree

 

Файл: show-adtreeou-ps1.doc