Является ли этот сценарий powershell хорошим способом найти владельца, а затем изменить владельца

Я написал сценарий Powershell v3 для вывода списка всех файлов, принадлежащих пользователю на нашем файловом сервере. Он работал в течение 3 часов, прежде чем я остановил его. Он должен пройти через 619 238 файлов и 57 452 папки (517 ГБ). Какого порядка это должно быть? Есть ли способ улучшить скорость?

Я пытался сделать это с помощью пайпов, но ничего не получилось.

Он работает на виртуальной машине Vmware под управлением Windows 2009 R2 SP1 с 4 ГБ памяти. Когда я запускаю его, он использует около 60% процессора.

Вот код, который я написал. Я новичок в Powershell, но у меня большой опыт работы с Perl. Мои коллеги сказали написать bat-файл.

<#
.SYNOPSIS
   C:\ams\psscripts\list-files.ps1
.DESCRIPTION
   List all the files that a given user owns
.PARAMETER none
   username: user 
   logfile: path to log file. This is optional. If omitted the the log file is created "u:\scratch\<$username>-files.txt
.EXAMPLE
    C:\ams\psscripts\list-files.ps1 plo
    Example: C:\ams\psscripts\list-files.ps1 plo u:\scratch\log.txt

#>

param (
    [string]$username,
    [string]$logfile
    )


# Load modules
Set-ExecutionPolicy Unrestricted
Import-Module ActiveDirectory
Add-PSSnapin Quest.ActiveRoles.ADManagement

function printHelp {
    Write-Host "This script will find all the files owned by a user. It scans \\dfs\groups"
    Write-Host "C:\ams\psscripts\list-files.ps1 user logfile (optional)"
    Write-Host "Example: C:\ams\psscripts\list-files.ps1 plo"
    Write-Host "Example: C:\ams\psscripts\list-files.ps1 plo u:\scratch\log.txt"
}

if ($logfile -eq "") {
    $logfile = "u:\scratch\" + $username + "-files.txt"
    Write-Host "Setting log file to $logfile"
}

# you must use a UNC path
[String]$path = "\\dfs\u$\groups"
[String]$AD_username = "AMS\" + $username

# check that we have a valid AD user
if (!(Get-QADUser $AD_username)){
    Write-Host "ERROR: Not a valid AD User: $AD_username"
    Exit 0
}

Write-Output "Listing all files owned by $username from $path" | Out-File -FilePath $logfile 
Write-Host "Listing all files owned by $username from $path"
$d = Get-Date
Write-Output $d | Out-File -FilePath $logfile -Append

$files = Get-ChildItem $path -Recurse
Foreach ($file in $files)
{
    $f = Get-Acl $file.FullName

    $d = [string]::Compare($file.FullName, $username, $True)
    if (($f.Owner -eq $username) -or ($f.Owner -eq $AD_username))
    {
        Write-Host "$file.FullName"
        Write-Output $file.FullName | Out-File -FilePath $logfile -Append
    }
}

Write-Host "Completed"
exit 0

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

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

$username=”dnp”
$domain=”ams”
$ID = new-object System.Security.Principal.NTAccount($domain, $username)

# file to change owner. must be UNC path
$path = "\\dfs\c$\ams\psscripts\test.txt"
write-host $path
$acl = get-acl $path
$acl.SetOwner($ID)
set-acl -path $path -aclObject $acl

Спасибо, Дэн


person DanP    schedule 09.08.2013    source источник


Ответы (2)


Я не знаю, какой порядок величины это должно иметь, но для повышения скорости вы можете начать с удаления избыточного сравнения в цикле foreach:

$d = [string]::Compare($file.FullName, $username, $True)

Сравнение строк требует больших затрат, и вы не используете $d. Вы также сравниваете с $username или «AMS\» + $username, что опять-таки дорого. Я не понимаю, почему вам нужно сравнивать с обоими. Я изменил ваш сценарий, чтобы добавить к нему синхронизацию. Я бы рекомендовал попробовать это на подмножестве файлов, чтобы получить некоторые эмпирические данные, чтобы определить, сколько времени займет полный набор. Имейте в виду, что общий размер файлов в этом случае не имеет значения, поскольку вы обрабатываете не их, а только их свойства.

<#
.SYNOPSIS
   C:\ams\psscripts\list-files.ps1
.DESCRIPTION
   List all the files that a given user owns
.PARAMETER none
   username: user 
   logfile: path to log file. This is optional. If omitted the the log file is created "u:\scratch\<$username>-files.txt
.EXAMPLE
    C:\ams\psscripts\list-files.ps1 plo
    Example: C:\ams\psscripts\list-files.ps1 plo u:\scratch\log.txt

#>

param (
    [string]$username,
    [string]$logfile
    )


# Load modules
Set-ExecutionPolicy Unrestricted
#Import-Module ActiveDirectory
#Add-PSSnapin Quest.ActiveRoles.ADManagement

function printHelp {
    Write-Host "This script will find all the files owned by a user. It scans \\dfs\groups"
    Write-Host "C:\ams\psscripts\list-files.ps1 user logfile (optional)"
    Write-Host "Example: C:\ams\psscripts\list-files.ps1 plo"
    Write-Host "Example: C:\ams\psscripts\list-files.ps1 plo u:\scratch\log.txt"
}

#StopWatch
$stopWatch = New-Object System.Diagnostics.Stopwatch
$stopWatch.Start()

if ($logfile -eq "") {
    $logfile = "e:\scratch\" + $username + "-files.txt"
    Write-Host "Setting log file to $logfile"
}

# you must use a UNC path
[String]$path = "\\test-server\testfolder\subfolder"
[String]$AD_username = "AMS\" + $username

# check that we have a valid AD user
if (!(Get-QADUser $AD_username)){
    Write-Host "ERROR: Not a valid AD User: $AD_username"
    Exit 0
}

Write-Output "Listing all files owned by $username from $path" | Out-File -FilePath $logfile 
Write-Host "Listing all files owned by $username from $path"
$d = Get-Date
Write-Output $d | Out-File -FilePath $logfile -Append

$stopWatch.Stop()
Write-Output ("Setup time: {0}." -f $stopWatch.Elapsed) | Out-File -FilePath $logfile -Append
$stopWatch.Reset()

$stopWatch.Start()
$files = Get-ChildItem $path -Recurse
$stopWatch.Stop()
Write-Output ("Got {0} files to process, took {1}" -f $files.Count, $stopWatch.Elapsed) | Out-File -FilePath $logfile -Append
$stopWatch.Reset()

$stopWatch.Start()
Foreach ($file in $files)
{
    $f = Get-Acl $file.FullName

    #$d = [string]::Compare($file.FullName, $username, $True)
    #if (($f.Owner -eq $username) -or ($f.Owner -eq $AD_username))
    if ($f.Owner -eq $AD_username)
    {
        Write-Host ("{0}" -f $file.FullName)
        Write-Output $file.FullName | Out-File -FilePath $logfile -Append
    }
}
$stopWatch.Stop()
Write-Output ("Processed {0} files, took {1}" -f $files.Count, $stopWatch.Elapsed) | Out-File -FilePath $logfile -Append

Write-Host "Completed"
exit 0

Это дало следующие результаты в нашей инфраструктуре:
Получено 37803 файла для обработки, заняло 00:00:57.5834897
Обработано 37803 файла, заняло 00:10:42.2988004

Исходному коду потребовалось 15 минут для обработки того же количества файлов:
обработано 37 803 файла, затрачено 00:15:04.1024350.

Добавлен @GeorgeR.Jenkins в построение строки памяти, но это не привело к значительному сокращению времени обработки:
Обработано 37803 файла, заняло 00:10:26.7815446

Интересно, что попытка передать get-childitem в предложение where не улучшила производительность. Использование

$files = Get-ChildItem $path -Recurse | where {(Get-Acl $_.FullName).Owner -eq $AD_username} <br/>

который вернет только файлы с правильным владельцем, поэтому обработка не потребовалась, позже выдал:
Получил 46 файлов для обработки, занял 00:13:51.4940596

Все это говорит о том, что если вы работаете в аналогичной мне инфраструктуре, я ожидаю, что при самой высокой скорости, которую я видел, которая составляет 59 файлов в секунду, ваши 619 238 файлов займут около 175 минут. Скорость, которую я получил с вашим исходным кодом, составляла 42 файла в секунду, что заняло бы 246 минут. Опять же, я бы посоветовал запустить в вашей системе небольшое подмножество файлов, чтобы рассчитать, сколько времени потребуется, прежде чем будет запущен весь набор.

person Andrew Fraser    schedule 13.08.2013
comment
Спасибо за помощь, теперь работает быстрее. В строке Write-Output $file.FullName | Out-File -FilePath $logfile -Append есть ли способ записать $file.LastWriteTime без усечения пути? Я попробовал несколько способов без успеха. - person DanP; 26.08.2013
comment
С двумя простыми текстовыми файлами log.txt и file.txt я использовал это: $log = Get-Item log.txt $file = Get-Item fileFound.txt $textOutput = ("{0}, Last write time: {1}" -f $file.FullName, $file.LastWriteTime) Write-Host $textOutput Write-Output $textOutput | Out-File -FilePath $log -Append -Encoding ASCII Просто сначала создайте строку, которую хотите записать, а затем запишите ее в свой файл. - person Andrew Fraser; 28.08.2013
comment
Большое спасибо. Я не совсем понял, что ты сделал. Итак, я сделал это: $textOutput = ("{0}, Last write time: {1}" -f $file.FullName, $file.LastWriteTime); Write-Host $textOutput; Write-Output $textOutput | Out-File -FilePath $logfile -Append -Encoding ASCII; - person DanP; 30.08.2013
comment
Нет проблем, это в основном то же самое, что я сделал. - person Andrew Fraser; 02.09.2013

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

[string]$fileowned +=  $file.fullname|?{$f.owner -eq $username -or $f.owner -eq $AD_username}

После выхода из цикла запишите полученный $logfile на диск:

$fileowned|out-file $logfile

Кстати, вы используете $d дважды; один раз для даты получения и затем для строки сравнения.

person UsPeoples    schedule 12.08.2013