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://github.com/PoshKazun/Garbage/blob/master/Get-HandleName.ps1

В серверных операционных системах 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

Рассмотрим отправку Post запроса, на примере сайта http://pr.nca.by/. Для этого можно воспользоваться двумя командлетами Invoke-WebRequest/Invoke-RestMethod, оба которые поддерживают параметр –Method.

Форма запроса на сайте выглядит:

form

 

Для получение параметров, которые требуется передать в теле post запроса , существует большое количество различных инструментов:

1. Встроенные средства браузеров, на примере IE/Google Chrome

Для запуска  Developer Tools в IE нажмите F12 или перейдите в меню

Tools –> F12 Developer Tools

IE_DV12

Переходим в раздел Network – нажимаем F5 и выполняем запрос:

IE_NET

 

Выбираем запрос и смотрим результаты:

IE_HR

IE_BODY

 

В Google Chrome для запуска инструментов разработчика нажмите F12 или перейдите в меню:

CH_DV12

CH_REQ

 

В Google Chrome удобно сделано копирование и можно сразу получить результат для утилиты curl.

CH_SV


curl "http://pr.nca.by/edc63526a304744e5976afdac72281a6.a_request.show_prices_count_test.xml" -H "Origin: http://pr.nca.by" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4" -H "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36" -H "Content-type: application/x-www-form-urlencoded" -H "Accept: */*" -H "Referer: http://pr.nca.by/" -H "Cookie: atereq=edc63526a304744e5976afdac72281a6; __utmt=1; __utma=97320926.45800996.1472894768.1472894768.1472916142.2; __utmb=97320926.1.10.1472916142; __utmc=97320926; __utmz=97320926.1472894768.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)" -H "Connection: keep-alive" --data "v_param=3&v_objnum=17030&v_date_begin=1.7.2016&v_date_end=3.9.2016&v_sq_begin=20&v_sq_end=200&v_purpose=40101&v_soato=5000000000&v_typ=3&v_name=&v_pr_b=25000&v_pr_e=250000&v_cur=4&v_url=http//pr.nca.by/&v_room=&v_floor_beg=&v_floor_end=&v_floor_number_beg=&v_floor_number_end=&v_input_date_beg=&v_input_date_end=&v_purpose_KSforIP=-1&v_wallmat=" --compressed

 

2. Программа-анализатор трафика WireShark

Фильтр: http && ip.dst_host == 93.125.111.85 (по HTTP и IP адресу назначения)

http && http.host contains «nca.by» (по HTTP и содержанию в хосте шаблона)

wr

 

3. Прокси программа для работы с веб – Fiddler

f1

 

Тело запроса:

f2

Ответ, как в текстовой форме, так и в XML:

f3

 

Теперь когда у нас есть все данные, мы можем сформировать Post запрос в PowerShell.

Чтобы отправить запрос на сайт, нам надо получить url-адрес, для этого мы должны перейти на сайт http://pr.nca.by/ , сохранить сессию в переменную. В cookies мы получаем значение atereq, которое участвует в формировании запроса.  И url запроса, должен иметь вид:

http://pr.nca.by/{atereq}.a_request.show_prices_count_test.xml

 

f4

 

$url = "http://pr.nca.by/"
$ct  = "application/x-www-form-urlencoded"
$s = Invoke-WebRequest $url -SessionVariable sb
$val = $sb.Cookies.GetCookies($url).Value
$purl = "${url}$val.a_request.show_prices_count_test.xml"
$r = Invoke-WebRequest $purl -ContentType $ct -WebSession $sb -Method Post -Body @{
	"v_param"="3"
	"v_objnum"="17030"
	"v_date_begin"="1.7.2016"
	"v_date_end"="31.7.2016"
	"v_sq_begin"="20"
	"v_sq_end"="200"
	"v_purpose"="40101"
	"v_soato"="5000000000"
	"v_typ"="3"
	"v_name"=""
	"v_pr_b"="25000"
	"v_pr_e"="2500000"
	"v_cur"="4"
	"v_url"="http//pr.nca.by/"
	"v_room"=""
	"v_floor_beg"=""
	"v_floor_end"=""
	"v_floor_number_beg"=""
	"v_floor_number_end"=""
	"v_input_date_beg"=""
	"v_input_date_end"=""
	"v_purpose_KSforIP"="-1"
	"v_wallmat"=""
}
([xml]$r.Content).response.rowcount_all

Примерный вывод:
PS > $r.Content
375

PS > ([xml]$r.Content).response.rowcount_all
375

При использовании командлета Invoke-RestMethod мы получим готовый ответ  в xml:

$r = Invoke-RestMethod $purl -ContentType $ct -WebSession $sb -Method Post .....

PS > $r
xml                            response
---                            --------
version="1.0" encoding="utf-8" response


PS > $r.response
type            rowcount_all
----            ------------
show_info_count 375


PS > $r.response.rowcount_all
375

В данной статье, рассмотрим 3 способа:

  1. Предоставление права SeRemoteShutdownPrivilege
  2. Делегирование прав WMI
  3. Использование Windows Powershell Remoting / JEA

Наш стенд:

  • 2 клиентских ОС – Windows 10
  • 1 DC – Windows Server 2012 R2
  • Администратор: Contoso\Administrator
  • Пользователь: Contoso\User1

I. Первый способ – это  предоставление права SeRemoteShutdownPrivilege

Force shutdown from a remote system — Этот параметр безопасности определяет, каким пользователям разрешено завершать работу компьютера из удаленного расположения в сети. Это позволяет членам группы администраторов или конкретных пользователей для управления компьютерами (для задач, таких как перезапуск) из удаленного расположения.

Для предоставления SeRemoteShutdownPrivilege мы можем использовать: GPO, ntrights, secedit и т.д.

Утилита ntrights входит в комплект Windows Server 2003 Resource Kit Tools . После установки комплекта , выполняем:

ntrights -r SeRemoteShutdownPrivilege -u Contoso\User1

Другая утилита, которая поставляется по умолчанию является —  secedit.

При использовании secedit, потребуется подготовить шаблон.

SECEDIT /configure /db secedit.sdb /cfg "c:\SEC_TEMPLATE.inf"

[Unicode]
Unicode=yes

[Registry Values]

[Privilege Rights]

SeRemoteShutdownPrivilege = "Administrators","Contoso\User1"

[Version]

signature="$CHICAGO$"
Revision=1

[Profile Description]
Description=Local Security Template

SECEDIT /configure /db secedit.sdb /cfg "c:\SEC_TEMPLATE.inf"

 

Для предоставления права вручную:

lgpo_30082016

 

Предоставим право SeRemoteShutdownPrivilege через PowerShell. Для этого нам потребуется отличный модуль — UserRights PowerShell Module .

1. Устанавливаем UserRights PowerShell Module

$fname = "UserRights.zip"            
$dpath = "$env:UserProfile\Documents\WindowsPowerShell\Modules\UserRights"            
$url = "https://gallery.technet.microsoft.com/scriptcenter/UserRights-PowerShell-1ff45589/file/124612/1/UserRights.zip"            
[Net.WebClient]::new().DownloadFile($url, $fname)            
Unblock-File $fname            
Expand-Archive -LiteralPath $fname -DestinationPath $dpath

 

2. Проверим у кого есть право SeRemoteShutdownPrivilege по умолчанию

PS > Get-UserRight -Privileges SeRemoteShutdownPrivilege

Right                     ComputerName Accounts
-----                     ------------ --------
SeRemoteShutdownPrivilege W10-CL2      BUILTIN\Administrators

3. Добавим право SeRemoteShutdownPrivilege для пользователя Contoso\User1

 
PS > Grant-UserRight -Account Contoso\User1 -Privileges SeRemoteShutdownPrivilege -PassThru 

Right                     ComputerName Accounts 
-----                     ------------ -------- 
SeRemoteShutdownPrivilege W10-CL2      {BUILTIN\Administrators, CONTOSO\User1}

 

4. Добавим правило исключения в firewall

Get-NetFirewallRule -DisplayName "File and Printer Sharing (SMB-In)" | Enable-NetFirewallRule

 

5. Выключаем удаленную машину

shutdown /m \\W10-CL2.Contoso.Com /s /t 0

 

 

Какие WinApi использует утилита shutdown:

Определим сигнатуру и выполним функцию. 
 
$Signature = @"
[DllImport("advapi32.dll")]
	public static extern bool InitiateSystemShutdown(
        [MarshalAs(UnmanagedType.LPStr)] string lpMachinename,
        [MarshalAs(UnmanagedType.LPStr)] string lpMessage,
        Int32 dwTimeout,
        bool bForceAppsClosed,
        bool bRebootAfterShutdown);
"@

$ShutDown = Add-Type -MemberDefinition $Signature -Name "Win32" -Namespace Win32Functions -PassThru
$ShutDown::InitiateSystemShutdown("W10-CL2.Contoso.Com","",0,$true,$false)

 

Можно использовать другой метод, который описан в статье — Weekend Scripter: Give Your PowerShell Console a Glassy Theme .

 


#region Module Builder
$Domain = [AppDomain]::CurrentDomain
$DynAssembly = New-Object System.Reflection.AssemblyName('PInvokeAssembly')
# Only run in memory
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run)
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('PInvokeModule', $False)
#endregion Module Builder
$TypeBuilder = $ModuleBuilder.DefineType('PInvoke', 'Public, Class')

$PInvokeMethod = $TypeBuilder.DefineMethod(
    'InitiateSystemShutdown', #Method Name
    [Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes
    [bool], #Method Return Type
    [Type[]] @([string],[string],[Int32],[bool],[bool]) #Method Parameters
)

$DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
$FieldArray = [Reflection.FieldInfo[]] @(
	[Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),
	[Runtime.InteropServices.DllImportAttribute].GetField('PreserveSig')
)

$FieldValueArray = [Object[]] @(
    'InitiateSystemShutdown', #CASE SENSITIVE!!
    $False
)

$CustomAttributeBuilder = New-Object Reflection.Emit.CustomAttributeBuilder(
    $DllImportConstructor,
	@('Advapi32.dll'),
	$FieldArray,
	$FieldValueArray
)

$PInvokeMethod.SetCustomAttribute($CustomAttributeBuilder)

[void]$TypeBuilder.CreateType()
[PInvoke]::InitiateSystemShutdown("W10-CL2.Contoso.Com","",0,$true,$false)

 

Для удаления права:

PS > Revoke-UserRight -Account Contoso\User1 -Privileges SeRemoteShutdownPrivilege -PassThru

Right                     ComputerName Accounts
-----                     ------------ --------
SeRemoteShutdownPrivilege W10-CL2      BUILTIN\Administrators

 

II. Второй способ — Делегирование прав WMI

Удаленный доступ к WMI по умолчанию разрешен только группе Администраторы. Т.к. право Access this computer from network по умолчанию назначено Domain Users для клиентских ос, то мы его трогать не будем.

Т.к. группе Distributed COM Users выданны нужные права, то нам будет достаточно только добавить туда пользователя или группу. Если Вам потребуется ограничить права более детально:

  1. Создайте группу и добавьте заданных пользователей
  2. Проверить, что группа имеет право — Access this computer from network user right
  3. Component Services (dcomcnfg.exe) -> Computers -> My Computer –> Propertiesdcom_30082016
  4. Проверить галочку Default Properties -> Enable Distributed COMdcom1_30082016
  5. COM Security tab -> Edit Limits секция Launch and Activation Permissions и добавить группу, указав Allow для Local Launch, Remote Launch, Local Activation, Remote Activationdcom2_30082016dcom3_30082016
  6. Открываем My Computer -> переходим к разделу DCOM Config находим Windows Management and Instrumentation -> Properties –> Security. В разделе Launch and Activation Permissions назначаем Allow для Local Launch, Remote Launch, Local Activation, Remote Activationdcom4_30082016
  7. Закрываем
  8. WMI Control(WmiMgmt.msc) –> Properties –> Security –> Root\Cimv2 –> Security. Добавляем права Allow для Remote Enablewmi_30082016

Как в начале было сказано, мы пойдет более простым путем.

1. Добавим пользователя «Contoso\User1» в группу «Remote Management Users»

Add-LocalGroupMember "Distributed COM Users" -Member "Contoso\User1"

2. Добавим правила в FireWall

Get-NetFirewallRule -DisplayName "Windows Management Instrumentation (WMI-IN)" | Enable-NetFirewallRule

 

3. Добавим право SeRemoteShutdownPrivilege для пользователя Contoso\User1 ,т.к. выключение происходит удаленно то требуется данное право. Для локального выключения – SeShutdownPrivilege.

PS > Grant-UserRight -Account Contoso\User1 -Privileges SeRemoteShutdownPrivilege -PassThru

Right                     ComputerName Accounts
-----                     ------------ --------
SeRemoteShutdownPrivilege W10-CL2      {BUILTIN\Administrators, CONTOSO\User1}

 

4. Добавим разрешения для namespace = root\cimv2, где расположен класс Win32_OperatingSystem


$Namespace = "root\cimv2"
$systemSecurity = Get-CimInstance -Namespace $Namespace -ClassName __SystemSecurity   
$oDacl = Invoke-CimMethod -InputObject $systemSecurity -MethodName GetSecurityDescriptor 

$sd = $oDacl.Descriptor

$domain = "Contoso"
$user = "User1"

$ntuser = New-Object System.Security.Principal.NTAccount($domain,$user) 

$trustee = New-CimInstance -Namespace root/cimv2 -ClassName Win32_Trustee -ClientOnly -Property @{
	Domain = $domain
	Name = $user
	SidString = $ntuser.Translate([Security.Principal.SecurityIdentifier]).Value
} 

# AceType Allow = 0 Deny = 1
# AccessMask 35 = Execute Methods,Enable Account,Remote Enable

$ace = New-CimInstance -Namespace root/cimv2 -ClassName Win32_Ace -ClientOnly -Property @{
	AceType=[uint32]0
	Trustee=$trustee
	AccessMask=[uint32]35
	AceFlags=[uint32]0
} 

[CIMInstance[]] $nDacl = $null 
foreach ($iAce in $sd.DACL) { 
	$nDacl += $iAce
}
 
$newDacl += $ace 
$sd.DACL = $newDacl 
                
Invoke-CimMethod -InputObject $systemSecurity -MethodName SetSecurityDescriptor -Arguments @{ 
	Descriptor = $sd 
}

 

5. Выключаем

Stop-Computer -ComputerName w10-cl2.contoso.com -Force

 

С переводом проекта PowerShell — https://github.com/PowerShell/PowerShell в разряд OpenSource, у нас появилась возможность посмотреть работу командлета Stop-Computer.

 


PS > Get-Command Stop-Computer | Format-List Module,ImplementingType

Module           : Microsoft.PowerShell.Management
ImplementingType : Microsoft.PowerShell.Commands.StopComputerCommand

Зная тип переходим на сайте github и в поиске указываем StopComputerCommad.

 

github_posh

 

 

Данный командлет  начинается в строке 3185 — #region Stop-Computer  и заканчивается 3606 — #endregion .

 

Командлет работает,как с DCOM, так и WSMAN.

PS > Get-Help Stop-Computer -Parameter Authentication

-Authentication <AuthenticationLevel>
    Указывает уровень проверки подлинности, используемый для подключения к WMI. (Stop-Computer использует WMI.) 
	Значение по умолчанию  Packet.

    Packet:          проверка подлинности COM на уровне пакетов.
	
    Значение по умолчанию                Packet (4)

	/// Packet = 4,
    [Alias("Authentication")]
    public AuthenticationLevel DcomAuthentication { get; set; } = AuthenticationLevel.Packet;

 

На платформе Windows Desktop , используется по умолчанию  DcomProtocol. Для поддержки других платформ(например Linux,MacOs) — WsmanProtocol.

 

Подробнее :

https://blogs.msdn.microsoft.com/dotnet/2014/12/04/introducing-net-core/

https://blogs.msdn.microsoft.com/dotnet/2015/02/03/coreclr-is-now-open-source/

[ValidateSet(ComputerWMIHelper.DcomProtocol, ComputerWMIHelper.WsmanProtocol)]
        public string Protocol { get; set; } = 
#if CORECLR
            //CoreClr does not support DCOM protocol
            // This change makes sure that the the command works seamlessly if user did not explicitly entered the protocol
            ComputerWMIHelper.WsmanProtocol;
#else
            ComputerWMIHelper.DcomProtocol;
#endif
 
PS > Get-Help Stop-Computer -Parameter Impersonation

-Impersonation <ImpersonationLevel>
    Задает уровень олицетворения, используемый при вызове WMI. (Stop-Computer использует WMI.) Значение по умолчанию 
    "Impersonate".

    Impersonate:  позволяет объектам использовать учетные данные вызывающей стороны.

	// Impersonate = 3,
    public ImpersonationLevel Impersonation { get; set; } = ImpersonationLevel.Impersonate;

Рассматривать работу в Job не будем. Метод Win32Shutdown определен:

uint32 Win32Shutdown (

[in] sint32 Flags,

[in] sint32 Reserved =

);

Если параметр -Force не указан, то используется флаг — 0x1,   иначе 0x5.

1 (0x1)

Shutdown — Shuts down the computer to a point where it is safe to turn off the power.

 

5 (0x5)

Forced Shutdown (1 + 4) — Shuts down the computer to a point where it is safe to turn off the power.

 




# Определяется флаг
	object[] flags = new object[] { 1, 0 };
            if (Force.IsPresent)
                flags[0] = 5;


# Вызов метода Win32Shutdown класса Win32_OperatingSystem с заданными параметрами
		
ConnectionOptions options = ComputerWMIHelper.GetConnectionOptions(DcomAuthentication, this.Impersonation, this.Credential);
    ManagementScope scope = new ManagementScope(ComputerWMIHelper.GetScopeString(computer, ComputerWMIHelper.WMI_Path_CIM), options);
    ObjectQuery query = new ObjectQuery("select * from " + ComputerWMIHelper.WMI_Class_OperatingSystem);
    using (_searcher = new ManagementObjectSearcher(scope, query, enumOptions))
    {
        foreach (ManagementObject obj in _searcher.Get())
         {
            using (obj)
             {
                object result = obj.InvokeMethod("Win32shutdown", flags);

 

Для просмотра internal properties класса ComputerWMIHelper:

PS > $class = [Reflection.Assembly]::Load(
		"Microsoft.PowerShell.Commands.Management").GetType(
			"Microsoft.PowerShell.Commands.ComputerWMIHelper")

PS > $class.GetFields([Reflection.BindingFlags]"NonPublic,Static").Name
NetBIOSNameMaxLength
WMI_Class_SystemRestore
WMI_Class_OperatingSystem
WMI_Class_Service
WMI_Class_ComputerSystem
WMI_Class_PingStatus
WMI_Path_CIM
WMI_Path_Default
ErrorCode_Interface
ErrorCode_Service
SE_SHUTDOWN_NAME
SE_REMOTE_SHUTDOWN_NAME
DcomProtocol
WsmanProtocol
CimUriPrefix
CimOperatingSystemNamespace
CimOperatingSystemShutdownMethod
CimQueryDialect
localhostStr

PS > $class.GetField(
	"WMI_Class_OperatingSystem",[Reflection.BindingFlags]"NonPublic,Static").GetValue($class)
Win32_OperatingSystem

PS > $class.GetFields([Reflection.BindingFlags]"NonPublic,Static").Foreach({
	[pscustomobject]@{
		Name = $_.Name
		Value = $class.GetField($_.Name,[Reflection.BindingFlags]"NonPublic,Static").GetValue($class)
	}
})

Name                                                                                Value
----                                                                                -----
NetBIOSNameMaxLength                                                                   15
WMI_Class_SystemRestore                                                     SystemRestore
WMI_Class_OperatingSystem                                           Win32_OperatingSystem
WMI_Class_Service                                                           Win32_Service
WMI_Class_ComputerSystem                                             Win32_ComputerSystem
WMI_Class_PingStatus                                                     Win32_PingStatus
WMI_Path_CIM                                                                  \root\cimv2
WMI_Path_Default                                                            \root\default
ErrorCode_Interface                                                                  1717
ErrorCode_Service                                                                    1056
SE_SHUTDOWN_NAME                                                      SeShutdownPrivilege
SE_REMOTE_SHUTDOWN_NAME                                         SeRemoteShutdownPrivilege
DcomProtocol                                                                         DCOM
WsmanProtocol                                                                       WSMan
CimUriPrefix                     http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2
CimOperatingSystemNamespace                                                    root/cimv2
CimOperatingSystemShutdownMethod                                            Win32shutdown
CimQueryDialect                                                                       WQL
localhostStr                                                                    localhost

 

III. Третий способ —  Windows Powershell Remoting / JEA

Отличное описание Windows PowerShell endpoints в серии статей — https://blogs.technet.microsoft.com/heyscriptingguy/2014/03/31/introduction-to-powershell-endpoints/

Перейдем сразу к реализации, т.к. вариантов много, рассмотрим один из них.

1. Включаем PowerShell Remoting

PS > Enable-PSSessionConfiguration -Force
WinRM has been updated to receive requests.
WinRM service type changed successfully.
WinRM service started.

WinRM has been updated for remote management.
WinRM firewall exception enabled.

2. Зарегистрируем endpoint с учетными данными администратора

New-PSSessionConfigurationFile –Path ShutDownEnd.pssc –SessionType RestrictedRemoteServer -LanguageMode NoLanguage -VisibleCmdlets Stop-Computer 
Register-PSSessionConfiguration -Name ShutDown -Path ShutDownEnd.pssc –ShowSecurityDescriptorUI -RunAsCredential Contoso\Administrator

3. Выключаем

$session = New-PSSession -ConfigurationName ShutDown -ComputerName W10-CL2.contoso.com
Invoke-Command -Session $session -ScriptBlock {Stop-Computer -Force}

 

 

Параметр ShowSecurityDescriptorUI вызывает окно где мы указываем права доступа, достаточно Execute(Invoke).

psc-30082016

Для автоматизации процесса, скачайте скрипт Add-PoShEndpointAccess.

Add-PoShEndpointAccess -SamAccountName "contoso\User1" -EndpointName ShutDown

 

И в качестве примера, приведу использование JEA (Just Enough Administration).

Just Enough Administration (JEA) — это технология безопасности, позволяющая делегировать администрирование в отношении всего, чем можно управлять через PowerShell. JEA позволяет сделать следующее:

  • Уменьшить число администраторов на компьютерах, используя виртуальные учетные записи, которые допускают выполнение привилегированных действий от имени обычных пользователей.
  • Ограничить доступные пользователям действия, указав, какие командлеты, функции и внешние команды могут выполнять пользователи.
  • Лучше понять, что делают ваши пользователи, используя детализированные записи с «подсматриванием» для команд, выполняемых пользователем за время сеанса.

Подробнее:

https://msdn.microsoft.com/en-us/powershell/jea/readme

https://github.com/PowerShell/JEA

# Fields in the role capability
$MaintenanceRoleCapabilityCreationParams = @{
    Author = 'Contoso Admin'
    CompanyName = 'Contoso'
    VisibleCmdlets = @{ Name = 'Stop-Computer' ; Parameters =  @{ Name = 'Force' } }
}

# Create the demo module, which will contain the maintenance Role Capability File
New-Item -Path "$env:ProgramFiles\WindowsPowerShell\Modules\Demo_Module" -ItemType Directory
New-ModuleManifest -Path "$env:ProgramFiles\WindowsPowerShell\Modules\Demo_Module\Demo_Module.psd1"
New-Item -Path "$env:ProgramFiles\WindowsPowerShell\Modules\Demo_Module\RoleCapabilities" -ItemType Directory

# Create the Role Capability file
New-PSRoleCapabilityFile -Path "$env:ProgramFiles\WindowsPowerShell\Modules\Demo_Module\RoleCapabilities\Maintenance.psrc" @MaintenanceRoleCapabilityCreationParams

# Determine domain
$domain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain

# Replace with your non-admin group name
$NonAdministrator = "$domain\User1"

# Specify the settings for this JEA endpoint
# Note: You will not be able to use a virtual account if you are using WMF 5.0 on Windows 7 or Windows Server 2008 R2
$JEAConfigParams = @{
    SessionType = 'RestrictedRemoteServer'
    RoleDefinitions = @{
        $NonAdministrator = @{ RoleCapabilities = 'Maintenance' }
    }
    TranscriptDirectory = "$env:ProgramData\JEAConfiguration\Transcripts"
}

# Set up a folder for the Session Configuration files
if (-not (Test-Path "$env:ProgramData\JEAConfiguration"))
{
    New-Item -Path "$env:ProgramData\JEAConfiguration" -ItemType Directory
}

# Specify the name of the JEA endpoint
$sessionName = 'JEA_ShutDown'

if (Get-PSSessionConfiguration -Name $sessionName -ErrorAction SilentlyContinue)
{
    Unregister-PSSessionConfiguration -Name $sessionName -ErrorAction Stop
}

New-PSSessionConfigurationFile -Path "$env:ProgramData\JEAConfiguration\JEADemo.pssc" @JEAConfigParams

# Register the session configuration
Register-PSSessionConfiguration -Name $sessionName -Path "$env:ProgramData\JEAConfiguration\JEADemo.pssc" -RunAsCredential Contoso\Administrator
}

$session = New-PSSession -ConfigurationName ShutDown -ComputerName W10-CL2.contoso.com
Invoke-Command -Session $session -ScriptBlock {Stop-Computer -Force}

Дополнительная информация:

1) Setting up a Remote WMI Connection  — https://msdn.microsoft.com/en-us/library/aa822854(v=vs.85).aspx

 

2) DSC Resource to manage WMI Namespace Security — https://github.com/PowerShell/WmiNamespaceSecurity/blob/master/WmiNamespaceSecurity.psm1

 

3) Privilege Constants — https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx

 

4) Force shutdown from a remote system — https://technet.microsoft.com/en-us/library/dn221951(v=ws.11).aspx

 

5) Access to WMI Namespaces — https://msdn.microsoft.com/en-us/library/aa822575(v=vs.85).aspx

 

6) Windows PowerShell endpoints — https://blogs.technet.microsoft.com/heyscriptingguy/2014/03/31/introduction-to-powershell-endpoints/

 

7) Just Enough Administration — https://msdn.microsoft.com/en-us/powershell/jea/readme

 

8) Constrained PowerShell endpoints – Visible cmdlets, session types, and language modes — https://4sysops.com/archives/constrained-powershell-endpoints-visible-cmdlets-session-types-and-language-modes/

 

9) Using Powershell and Reflection API to invoke methods from .NET Assemblies — https://blog.netspi.com/using-powershell-and-reflection-api-to-invoke-methods-from-net-assemblies/

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

Ресурс https://fsrm.experiant.ca/ предоставляет шаблоны для противодействия ransomware. Скрипты для автоматического добавления политик — https://github.com/m-dwyer/CryptoBlocker

Начиная с Windows Server 2012 и выше доступен модуль для работы с FSRM — https://technet.microsoft.com/en-us/library/jj900651(v=wps.630).aspx и новые WMI классы — https://msdn.microsoft.com/en-us/library/hh706679(v=vs.85).aspx. Поэтому простой перенос модуля с Windows Server 2012 на 2008 R2 не получится.

И задача сводится к простым действиям:

$url = "https://fsrm.experiant.ca/api/v1/combined"            
$pat = @((Invoke-WebRequest -Uri $url).content | convertfrom-json | % {$_.filters})            
Set-FsrmFileGroup -Name "RMS" -IncludePattern $list -IncludePattern $pat

В Windows Server 2008 R2 доступна утилита filescrn.exe для обновления file group, попробуем ей  воспользоваться.

PS > $tmpl = @((Invoke-WebRequest -Uri "https://fsrm.experiant.ca/api/v1/combined").Content | ConvertFrom-JSON | Foreach {$_.filters})            
PS > filescrn Filegroup modify /FileGroup:"RSM" /Members:"$($tmpl -join '|')"            
Параметр задан неверно.

Получаем ошибку. Опытным путем установим, сколько значений мы можем передать параметру Members. У меня получилось = 216

PS > ($tmpl.Count-1)..1 | Foreach {            
>>     $temp = $tmpl[0..$_] -join "|"            
>>     filescrn Filegroup Add /FileGroup:"RSM" /Members:"$temp" | Out-Null            
>>     if($?) {            
>>         $_            
>>         break            
>>     }            
>> }            
>>            
216

Какие у нас есть варианты обновления больше 216 элементов?

  • 1) Экспорт и последующий импорт xml-файла c более поздних систем начиная с Windows Server 2012 и выше

    2) Добавление данных в xml-файла с последующим импортом3) Создание нескольких групп в которых меньше 216 элементов

    4) Использовать FSRM API

  • 5) Вручную

Рассмотрим 4 вариант для обновления данных. В этом нам поможет Com класс FSRM.FsrmFileGroupManager — https://msdn.microsoft.com/en-us/library/bb625542(v=vs.85).aspx

Для преобразования \uFFFF воспользуемся простыми методами:

PS > $tmpl[120]            
Comment d\u00e9bloquer mes fichiers.txt            
            
PS > [char]0x00e9            
é            
            
PS > [regex]::Unescape("Comment d\u00e9bloquer mes fichiers.txt")             
Comment débloquer mes fichiers.txt


Function Update-FsrmFileGroup {            
             
 [CmdletBinding()]             
 param (            
  [Parameter(Mandatory=$True)]            
  # Название группы            
  [String]$GroupName             
 )            
             
 # Исключения для метода GetFileGroup            
 # https://msdn.microsoft.com/en-us/library/cc422727.aspx            
 $rvalue = @{            
  "0x80045301" = "`nThe specified file group could not be found."            
  "0x80045308" = "`nThe specified name is not valid."            
  "0x8004530D" = "`nThe content of the name parameter exceeds the maximum length of 4,000 characters."            
  "0x80070057" = "`nThe fileGroup parameter is NULL."            
 }            
 # Скачиваем обновленный шаблон            
 # Для замены \uFFFF в "Comment d\u00e9bloquer mes fichiers.txt"            
 # Используем метод [regex]::Unescape            
 # [regex]::Unescape("Comment d\u00e9bloquer mes fichiers.txt")            
 # Comment débloquer mes fichiers.txt	            
 $url = "https://fsrm.experiant.ca/api/v1/combined"            
 $tmpl = (New-Object Net.WebClient).DownloadString($url).split(",[") -notmatch ":" | Foreach {            
  [regex]::Unescape(($_.Trim("}").Trim("]").Trim('"')))            
 }            
             
 # Создаем ComObject - 8f1363f6-656f-4496-9226-13aecbd7718f            
 #	$t = [Type]::GetTypeFromCLSID("8f1363f6-656f-4496-9226-13aecbd7718f")            
 #	$obj = [Activator]::CreateInstance($t)            
 #            
 $fsrm = New-Object -ComObject FSRM.FsrmFileGroupManager            
             
 try {            
  $fgroup = $fsrm.GetFileGroup($GroupName)            
 }            
 catch {            
  $ex = $_.Exception.InnerException.InnerException.Message.Split(":")[1].Trim()            
  Write-Error -Message "$($rvalue[$ex])" -Category InvalidData            
 }            
             
 if($fgroup) {            
  # Получаем список текущих элементов в группе            
  $members = $fgroup.Members            
  Write-Verbose "Количество элментов в группе: $($members.count)"            
              
  if($tmpl) {            
   Write-Verbose "Удаляем все элементы из группы"            
   $count = $members.count            
   for($i = 1 ; $i -le $count; $i++) {            
    $members.Remove(1)            
   }            
               
   Write-Verbose "Добавляем элементы $($tmpl.Count) в группу"            
   for($i = 0 ; $i -lt $tmpl.count; $i++) {            
    $members.Add($tmpl[$i])            
   }            
               
   Write-Verbose "Сохраняем изменения"            
   $fgroup.Members = $members            
   $fgroup.Commit()            
  }            
 }            
}

Проверим, правильность обновления.

PS > Update-FsrmFileGroup -GroupName RSM -Verbose            
ПОДРОБНО: Количество элментов в группе: 120            
ПОДРОБНО: Удаляем все элементы из группы            
ПОДРОБНО: Добавляем элементы 441 в группу            
ПОДРОБНО: Сохраняем изменения            
            
PS > (New-Object -ComObject FSRM.FsrmFileGroupManager).GetFileGroup($GroupName).Members.Count            
441

Обновление прошло успешно.

Для мониторинга подозрительной активности и определения проблемных пользователей, и проверки правильно ли работают настройки, нам поможет событие 8215 .

Description:

The system detected that user <user name> attempted to save <file> on <directory>. This file matches the «<file group name>» file group which is not permitted on the system.

PS > Get-WinEvent -FilterHashtable @{LogName="Application";ID=8215} -MaxEvents 1 -ComputerName srvfile            
            
TimeCreated  : 16.08.2016 10:23:11            
ProviderName : SRMSVC            
Id           : 8215            
Message      : Пользователь Contoso\sidorov попытался сохранить файл C:\Share\1123.777 в папке C:\ на сервере srvfile.             
    Этот файл принадлежит к группе файлов "RSM", которая не разрешена на сервере.