none
PowerShell Script GUI + Runspace RRS feed

  • Вопрос

  • Всем доброго времени суток!

    Есть скрипт + GUI форма для него. Суть такова, скрипт по клику на кнопку собирает информацию по Пользователям домена с помощью Get-ADUser и заполняет SamAccountName  элемент ComboBox, с отображением процесса в  ProgressBar.

    В организации много УЗ и процесс длителен, в результате чего форма и элементы не активны пока Get-ADUser не соберет информацию. Вижу единственным решением - это использовать Runspace\RunspacePool и хочу это реализовать, но пока Posh только изучаю и делаю первые шаги.

    Просьба помочь\подсказать решение по реализации Runspace  в скрипте (желательно с синхронизацией процесса с  ProgressBar, если это возможно).  Вот упрощенный вариант скрипта:

    Import-Module ActiveDirectory
    Add-Type -assembly System.Windows.Forms
    
    $main_form = New-Object System.Windows.Forms.Form
    $main_form.Text ='Form'
    $main_form.Width = 500
    $main_form.Height = 500
    $main_form.AutoSize = $true
    
    #Button "Count User:"
    $Button_User_Count = New-Object System.Windows.Forms.Button
    $Button_User_Count.Text = "Count Users:"
    $Button_User_Count.Location = New-Object System.Drawing.Point(10,10)
    $Button_User_Count.Size = New-Object System.Drawing.Size(100,20)
    $main_form.Controls.Add($Button_User_Count)
    
    #ProgressBar
    $ProgressBar_User = New-Object System.Windows.Forms.ProgressBar
    $ProgressBar_User.Maximum = 100
    $ProgressBar_User.Minimum = 0
    $ProgressBar_User.Location = new-object System.Drawing.Size(170,10)
    $ProgressBar_User.Size = New-Object System.Drawing.Size(220,23)
    $ProgressBar_User.AutoSize = $true
    $main_form.Controls.Add($ProgressBar_User)
    
    #ComboBox
    $ComboBox_User = New-Object System.Windows.Forms.ComboBox
    $ComboBox_User.Location  = New-Object System.Drawing.Point(10,40)
    $ComboBox_User.Width = 150
    $ComboBox_User.Sorted = $true
    $ComboBox_User.AutoCompleteSource = 'ListItems'
    $ComboBox_User.AutoCompleteMode = 'SuggestAppend'
    $main_form.Controls.Add($ComboBox_User)
    
    #Button Click
    $Button_User_Count.Add_Click(
    {
    #Clear ComboBox
    $ComboBox_User.Items.Clear();
    
    #Timeout
    Wait-Event -Timeout 1
    
    #Long command
    $Users = Get-ADUser -Filter * -Properties SamAccountName
    $i1 = 0
    
    Foreach ($User in $Users)
    {
    $i1++
    $ProgressBar_User.Value = (($i1/$Users.count)*100);
    $ComboBox_User.Items.Add($User.SamAccountName);
    }
    $ComboBox_User.Focus();
    }
    )
    
    $main_form.ShowDialog()


    • Изменено Tigerrrcub 5 января 2020 г. 12:43
    5 января 2020 г. 12:36

Ответы

  • Всем доброго времени суток!

    Есть скрипт + GUI форма для него. Суть такова, скрипт по клику на кнопку собирает информацию по Пользователям домена с помощью Get-ADUser и заполняет SamAccountName  элемент ComboBox, с отображением процесса в  ProgressBar.

    В организации много УЗ и процесс длителен, в результате чего форма и элементы не активны пока Get-ADUser не соберет информацию. Вижу единственным решением - это использовать Runspace\RunspacePool и хочу это реализовать, но пока Posh только изучаю и делаю первые шаги.

    Просьба помочь\подсказать решение по реализации Runspace  в скрипте (желательно с синхронизацией процесса с  ProgressBar, если это возможно).  Вот упрощенный вариант скрипта:

    Import-Module ActiveDirectory
    Add-Type -assembly System.Windows.Forms
    
    $main_form = New-Object System.Windows.Forms.Form
    $main_form.Text ='Form'
    $main_form.Width = 500
    $main_form.Height = 500
    $main_form.AutoSize = $true
    
    #Button "Count User:"
    $Button_User_Count = New-Object System.Windows.Forms.Button
    $Button_User_Count.Text = "Count Users:"
    $Button_User_Count.Location = New-Object System.Drawing.Point(10,10)
    $Button_User_Count.Size = New-Object System.Drawing.Size(100,20)
    $main_form.Controls.Add($Button_User_Count)
    
    #ProgressBar
    $ProgressBar_User = New-Object System.Windows.Forms.ProgressBar
    $ProgressBar_User.Maximum = 100
    $ProgressBar_User.Minimum = 0
    $ProgressBar_User.Location = new-object System.Drawing.Size(170,10)
    $ProgressBar_User.Size = New-Object System.Drawing.Size(220,23)
    $ProgressBar_User.AutoSize = $true
    $main_form.Controls.Add($ProgressBar_User)
    
    #ComboBox
    $ComboBox_User = New-Object System.Windows.Forms.ComboBox
    $ComboBox_User.Location  = New-Object System.Drawing.Point(10,40)
    $ComboBox_User.Width = 150
    $ComboBox_User.Sorted = $true
    $ComboBox_User.AutoCompleteSource = 'ListItems'
    $ComboBox_User.AutoCompleteMode = 'SuggestAppend'
    $main_form.Controls.Add($ComboBox_User)
    
    #Button Click
    $Button_User_Count.Add_Click(
    {
    #Clear ComboBox
    $ComboBox_User.Items.Clear();
    
    #Timeout
    Wait-Event -Timeout 1
    
    #Long command
    $Users = Get-ADUser -Filter * -Properties SamAccountName
    $i1 = 0
    
    Foreach ($User in $Users)
    {
    $i1++
    $ProgressBar_User.Value = (($i1/$Users.count)*100);
    $ComboBox_User.Items.Add($User.SamAccountName);
    }
    $ComboBox_User.Focus();
    }
    )
    
    $main_form.ShowDialog()

    проблема производительности у вас не в get-aduse (выделено жирным), а в foreach (выделено подчерктурым жирным

    правильно было бы не использовать foreach вовсе, а найти метод добавления значений в combobox пакетом

    попробовать это сделать можно так

    $ComboBox_User.Items.AddRange($($users.samaccountname))
    p.s. у get-aduser есть набор параметров которые всегда попадают в выдачу. SamAccountName среди них. Поэтому -properties samaccountname не делает ровным счетом ничего


    The opinion expressed by me is not an official position of Microsoft

    • Изменено Vector BCOModerator 5 января 2020 г. 13:46
    • Предложено в качестве ответа Vector BCOModerator 8 января 2020 г. 19:57
    • Помечено в качестве ответа Tigerrrcub 9 января 2020 г. 7:31
    5 января 2020 г. 13:42
    Модератор

Все ответы

  • Всем доброго времени суток!

    Есть скрипт + GUI форма для него. Суть такова, скрипт по клику на кнопку собирает информацию по Пользователям домена с помощью Get-ADUser и заполняет SamAccountName  элемент ComboBox, с отображением процесса в  ProgressBar.

    В организации много УЗ и процесс длителен, в результате чего форма и элементы не активны пока Get-ADUser не соберет информацию. Вижу единственным решением - это использовать Runspace\RunspacePool и хочу это реализовать, но пока Posh только изучаю и делаю первые шаги.

    Просьба помочь\подсказать решение по реализации Runspace  в скрипте (желательно с синхронизацией процесса с  ProgressBar, если это возможно).  Вот упрощенный вариант скрипта:

    Import-Module ActiveDirectory
    Add-Type -assembly System.Windows.Forms
    
    $main_form = New-Object System.Windows.Forms.Form
    $main_form.Text ='Form'
    $main_form.Width = 500
    $main_form.Height = 500
    $main_form.AutoSize = $true
    
    #Button "Count User:"
    $Button_User_Count = New-Object System.Windows.Forms.Button
    $Button_User_Count.Text = "Count Users:"
    $Button_User_Count.Location = New-Object System.Drawing.Point(10,10)
    $Button_User_Count.Size = New-Object System.Drawing.Size(100,20)
    $main_form.Controls.Add($Button_User_Count)
    
    #ProgressBar
    $ProgressBar_User = New-Object System.Windows.Forms.ProgressBar
    $ProgressBar_User.Maximum = 100
    $ProgressBar_User.Minimum = 0
    $ProgressBar_User.Location = new-object System.Drawing.Size(170,10)
    $ProgressBar_User.Size = New-Object System.Drawing.Size(220,23)
    $ProgressBar_User.AutoSize = $true
    $main_form.Controls.Add($ProgressBar_User)
    
    #ComboBox
    $ComboBox_User = New-Object System.Windows.Forms.ComboBox
    $ComboBox_User.Location  = New-Object System.Drawing.Point(10,40)
    $ComboBox_User.Width = 150
    $ComboBox_User.Sorted = $true
    $ComboBox_User.AutoCompleteSource = 'ListItems'
    $ComboBox_User.AutoCompleteMode = 'SuggestAppend'
    $main_form.Controls.Add($ComboBox_User)
    
    #Button Click
    $Button_User_Count.Add_Click(
    {
    #Clear ComboBox
    $ComboBox_User.Items.Clear();
    
    #Timeout
    Wait-Event -Timeout 1
    
    #Long command
    $Users = Get-ADUser -Filter * -Properties SamAccountName
    $i1 = 0
    
    Foreach ($User in $Users)
    {
    $i1++
    $ProgressBar_User.Value = (($i1/$Users.count)*100);
    $ComboBox_User.Items.Add($User.SamAccountName);
    }
    $ComboBox_User.Focus();
    }
    )
    
    $main_form.ShowDialog()

    проблема производительности у вас не в get-aduse (выделено жирным), а в foreach (выделено подчерктурым жирным

    правильно было бы не использовать foreach вовсе, а найти метод добавления значений в combobox пакетом

    попробовать это сделать можно так

    $ComboBox_User.Items.AddRange($($users.samaccountname))
    p.s. у get-aduser есть набор параметров которые всегда попадают в выдачу. SamAccountName среди них. Поэтому -properties samaccountname не делает ровным счетом ничего


    The opinion expressed by me is not an official position of Microsoft

    • Изменено Vector BCOModerator 5 января 2020 г. 13:46
    • Предложено в качестве ответа Vector BCOModerator 8 января 2020 г. 19:57
    • Помечено в качестве ответа Tigerrrcub 9 января 2020 г. 7:31
    5 января 2020 г. 13:42
    Модератор
  • Спасибо за предложенный вариант, затрачиваемое время на поиск и заполнение combobox действительно сократилось. Если не появятся альтернативные варианты решения, отмечу Ваш ответ как единственный верный!

    Но, всё-таки осталась проблема с "фризом" самой формы. Для меня это как раз самое актуальное. Как я уже писал в самом вопросе, количество УЗ в домене порядка несколько десятков тысяч и на сам процесс выполнения Get-ADUser затрачивается определенное время. Как мне кажется, напрашивается  только вариант с фоновым выполнением команды в Runspace, что бы не блокировать форму на этапе выполнения.

    Возможно я некорректно поставил сам вопрос,тогда конкретизирую его.

    Возможен ли вариант с выполнением задачи в фоне, что бы не блокировалась сама форма на этапе выполнения!?

    5 января 2020 г. 14:42
  • Спасибо за предложенный вариант, затрачиваемое время на поиск и заполнение combobox действительно сократилось. Если не появятся альтернативные варианты решения, отмечу Ваш ответ как единственный верный!

    Но, всё-таки осталась проблема с "фризом" самой формы. Для меня это как раз самое актуальное. Как я уже писал в самом вопросе, количество УЗ в домене порядка несколько десятков тысяч и на сам процесс выполнения Get-ADUser затрачивается определенное время. Как мне кажется, напрашивается  только вариант с фоновым выполнением команды в Runspace, что бы не блокировать форму на этапе выполнения.

    Возможно я некорректно поставил сам вопрос,тогда конкретизирую его.

    Возможен ли вариант с выполнением задачи в фоне, что бы не блокировалась сама форма на этапе выполнения!?

    покажите вывод 

    measure-command { get-aduser * }

    вы можете выпихнуть get-aduser во вторую строку, тем самым форма не будет блокироваться (более простое и правильное решение в данном случае)

    безусловно можно создать подзадачу и получить пользюков асинхронно, но запихивать то список в items вам все равно нужно синхронно, и как следствие нужно будет ждать уже там (проблема с фризом таким образом не решится, а лишь изменит форму)


    The opinion expressed by me is not an official position of Microsoft


    • Предложено в качестве ответа Vector BCOModerator 8 января 2020 г. 19:56
    • Изменено Vector BCOModerator 8 января 2020 г. 19:58
    • Отменено предложение в качестве ответа Tigerrrcub 9 января 2020 г. 7:30
    5 января 2020 г. 15:35
    Модератор
  • как ваши успехи?

    The opinion expressed by me is not an official position of Microsoft

    8 января 2020 г. 10:50
    Модератор
  • покажите вывод 

    measure-command { get-aduser * }

    PS C:\()> measure-command {Get-ADUser -Filter *}
    
    Days              : 0
    Hours             : 0
    Minutes           : 1
    Seconds           : 7
    Milliseconds      : 856
    Ticks             : 678568747
    TotalDays         : 0,000785380494212963
    TotalHours        : 0,0188491318611111
    TotalMinutes      : 1,13094791166667
    TotalSeconds      : 67,8568747
    TotalMilliseconds : 67856,8747

    Если рассматривать хронометраж на исходном коде, то получается приблизительно следующее:

    1. Get-ADUser -Filter * приблизительно 1 минута
    2. Цикл Foreach + ItemAdd+ProgressBar приблизительно еще 2 минуты
    3. Совокупно где-то 3 минуты

    Воспользовался Вашим предложением и вынес из цикла items, оставив цикл только для декорации ProgressBar, время реально сократилось в половину, так что Ваше предложение отмечу в качестве ответа.

    #Button Click
    $Button_User_Count.Add_Click(
    {
    #Clear ComboBox_PC
    $ComboBox_User.Items.Clear();
    
    #Timeout
    Wait-Event -Timeout 1
    
    $Timer = [System.Diagnostics.Stopwatch]::StartNew()
    $Timer.Start()
    
    #Long command
    $Users = Get-ADUser -Filter *
    $ComboBox_User.Items.AddRange($($Users.SamAccountName));
    $i = 0
    
    Foreach ($User in $Users)
    {
    $i++
    
    $ProgressBar_User.Value = (($i/$Users.Count)*100);
    }
    $Timer.Stop()
    write-host "Время выполнения команды $($Timer.Elapsed.Seconds) секунд"
    $ComboBox_User.Focus();
    }
    )


    вы можете выпихнуть get-aduser во вторую строку, тем самым форма не будет блокироваться (более простое и правильное решение в данном случае)

    Не совсем понял, куда вынести...


    безусловно можно создать подзадачу и получить пользюков асинхронно, но запихивать то список в items вам все равно нужно синхронно, и как следствие нужно будет ждать уже там (проблема с фризом таким образом не решится, а лишь изменит форму)

    Согласен, какое бы решение не использовать фриз ловить буду все равно на этапе Items, так что тут наверное вариантов решений особо-то и нету.

    И всё-таки...Для меня интересно узнать как использовать в такой простой задаче Runspace, как этот блок был бы реализован в коде, для меня это полезно, потому что у меня есть часть другого кода, где время выполнения в разы больше чем в приведённый в вопросе. Если есть возможность показать код с Runspace буду примного благодарен!


    Tigerrrcub

    9 января 2020 г. 7:30
  • цикл в вашем случае перебирает тысячи пользователей при этом ничего с ними не делая, а это время... если вы хотите ради фана прогресс бар то сделайте перебор от 1 до 100 - выиграете пару секунд на переборе

    Foreach ($i in @(0..100))
    {
          $ProgressBar_User.Value = $i;
    }

    В вашем оригинальном вопросе вы делаете import-module activedirectory в первой строке, и сразу после этого можете получить список всех пользюков, в таком случае не будет складываться впечатление что форма зависла (вместо этого будет ощущение что скрипт подвис 😆)

    асинхронная работа с субпроцессами осуществляется через start-job в который в качестве параметра подается scriptblock, после чего главную задачу можно с ним синхронизировать по времени через wait-job, а получение результатов происходит через receive-job

    Пример:

    $scr = {
       import-module activedirectory
       get-aduser *
    }
    start-job -name "receive-ad-user" -ScriptBlock $scr
    
    # тут длинный и долгий фрагмент который не зависит от пользюков 
    
    $Usrjob = get-job "receive-ad-user"
    Wait-Job $usrJob -timeout 180
    $Users = Receive-Job $usrJob
    
    Remove-Job $usrJob -Force
    
    # тут код который зависит от $Users

    недостатков в примере вагон и многие из них будут связаны с bad cases - что если джоб не запустится (по любой причине), что если вернет пустые результаты, что делать с ошибкамивнутри джоба и тд.

    Если ошибка внутри то вы о ней узнаете в момент receive-job, и возможно в таком случае ошибка сразу была бы предпочтительней выполнения первого закоментированного блока из примера

    Вообщем тут аспекты появляются не столько технические сколько логические


    The opinion expressed by me is not an official position of Microsoft

    9 января 2020 г. 8:28
    Модератор
  • асинхронная работа с субпроцессами осуществляется через start-job в который в качестве параметра подается scriptblock, после чего главную задачу можно с ним синхронизировать по времени через wait-job, а получение результатов происходит через receive-job

    Пример:

    $scr = {
       import-module activedirectory
       get-aduser *
    }
    start-job -name "receive-ad-user" -ScriptBlock $scr
    
    # тут длинный и долгий фрагмент который не зависит от пользюков 
    
    $Usrjob = get-job "receive-ad-user"
    Wait-Job $usrJob -timeout 180
    $Users = Receive-Job $usrJob
    
    Remove-Job $usrJob -Force
    
    # тут код который зависит от $Users

    недостатков в примере вагон и многие из них будут связаны с bad cases - что если джоб не запустится (по любой причине), что если вернет пустые результаты, что делать с ошибкамивнутри джоба и тд.

    Если ошибка внутри то вы о ней узнаете в момент receive-job, и возможно в таком случае ошибка сразу была бы предпочтительней выполнения первого закоментированного блока из примера

    Вообщем тут аспекты появляются не столько технические сколько логические


    The opinion expressed by me is not an official position of Microsoft

    Джобы я исключил для себя сразу, это не самый лучший вариант (ИМХО). Мог бы оформить это и по-другому, например так:

    start-job -ScriptBlock {Get-ADUser -Filter *} -Name myjob
    $ic = get-job -Name myjob | wait-job | Receive-Job
    $ComboBox_User.Items.AddRange($($ic.SamAccountName));
    Речь зашла за отдельный фоновый поток в Runspace с возвратом полученного результата в основной поток и реализацию этого решения в виде кода. Вот это мне больше всего интересно.


    Tigerrrcub

    9 января 2020 г. 9:11

  • Джобы я исключил для себя сразу, это не самый лучший вариант (ИМХО). Мог бы оформить это и по-другому, например так:

    start-job -ScriptBlock {Get-ADUser -Filter *} -Name myjob

    #>>>>>> если ничего нету тут то затея не имеет смысла <<<<<<< $ic = get-job -Name myjob | wait-job | Receive-Job $ComboBox_User.Items.AddRange($($ic.SamAccountName));

    Речь зашла за отдельный фоновый поток в Runspace с возвратом полученного результата в основной поток и реализацию этого решения в виде кода. Вот это мне больше всего интересно.


    Tigerrrcub

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

    что касается runspace то по сути (насколько я понял почитав о технологии) вы получаете туже функциональность в немного другой обертке, и суть выиграша runspace перед job в вашей задаче для меня не очевидна (кроме использования с# стиля вместо использования командлетов если это считать "+")

    UPD Пример задержек на пустом фориче который не делает ничего кроме перебора $i по инкрименту

    measure-command {foreach ($i in @(1..100000000)){}}
    
    Days              : 0
    Hours             : 0
    Minutes           : 1
    Seconds           : 27
    Milliseconds      : 266
    Ticks             : 872669030
    TotalDays         : 0.00101003359953704
    TotalHours        : 0.0242408063888889
    TotalMinutes      : 1.45444838333333
    TotalSeconds      : 87.266903
    TotalMilliseconds : 87266.903


    The opinion expressed by me is not an official position of Microsoft

    9 января 2020 г. 11:30
    Модератор