我正试图使用RunspacePools导入联系人,但我很难让它正常工作。如果我把它从运行空间逻辑中去掉,它工作得很好,只是需要很长时间。我真的很想使用runspacepools来加快导入过程,并使其运行多线程,从而加快导入速度。平均而言,每个用户每次导入大约需要5-6分钟,而我有大约500个用户,因此运行可能需要3000分钟。
这是我目前拥有的:
#---------------------------------------------
. $rootPathUpdateContactsUpdateContacts.ps1
# Set up runspace pool
$RunspacePool = [runspacefactory]::CreateRunspacePool(1,10)
$RunspacePool.Open()
# Assign new jobs/runspaces to a variable
$Runspaces = foreach ($User in $Users)
{
# Create new PowerShell instance to hold the code to execute, add arguments
$PSInstance = [powershell]::Create().AddScript({
$Users | ForEach{ UpdateContacts($_) }
}).AddParameter('$_')
# Assing PowerShell instance to RunspacePool
$PSInstance.RunspacePool = $RunspacePool
# Start executing asynchronously, keep instance + IAsyncResult objects
New-Object psobject -Property @{
Instance = $PSInstance
IAResult = $PSInstance.BeginInvoke()
Argument = $User
}
}
# Wait for the the runspace jobs to complete
while($Runspaces |Where-Object{-not $_.IAResult.IsCompleted})
{
Start-Sleep -Milliseconds 500
}
# Collect the results
$Results = $Runspaces |ForEach-Object {
$Output = $_.Instance.EndInvoke($_.IAResult)
New-Object psobject -Property @{
User = $User
}
}
而我的";UpdateContacts.ps1";文件如下:
Function UpdateContacts($User)
{
Write-host "Importing Contacts. This could take several minutes."
#FirstName, MiddleName, LastName, DisplayName, SamAccountName, Email, Mobile, TelephoneNumber, Title, Dept, Company, Photo, ExtensionAttribute2
$ContactsBody = @"
{
"givenName" : "$($User.FirstName)",
"middleName" : "$($User.MiddleName)",
"surname" : "$($User.LastName)",
"displayName" : "$($User.DisplayName)",
"jobTitle" : "$($User.Title)",
"companyName" : "$($User.Company)",
"department" : "$($User.Dept)",
"mobilePhone" : "$($User.Mobile)",
"homePhones" : ["$($User.TelephoneNumber)"],
"emailAddresses":
[
{
"address": "$($User.Email)",
"name": "$($User.DisplayName)"
}
]
}
"@
Try
{
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/$UPN/contactFolders/$folderId/contacts" -Headers $headers -Body $ContactsBody -Method Post -ContentType 'application/json' | Out-Null
#After each user clear the info
$User = $NULL
}
Catch
{
if($error)
{
$User
$error
pause
}
$_.Exception.Message
Write-Host "--------------------------------------------------------------------------------------"
$_.Exception.ItemName
}
}
感谢您的帮助。
编辑:这是完整的脚本(除了ContactUploader.ps1脚本。该函数在一个单独的脚本中,但整个代码(function(发布在上面(。
CLS
##################### Import Thread/Job Module to perform multithreading #####################
if(!(Get-Module -ListAvailable -Name ThreadJob))
{
$NULL = Install-Module -Name ThreadJob -Scope CurrentUser -Force -Confirm:$False
}
##################### ------------------------------------------------------------- #####################
#Root Path
$rootPath = $(Split-path $MyInvocation.MyCommand.path -Parent)
#Prevent connection from closing on us when we use "Invoke-RestMethod"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
Add-Content "$rootPathprogress.txt" ""
Add-Content "$rootPathprogress.txt" "********** Starting Script $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
##################### Connect to Microsoft Graph API and configure all of our Variables #####################
Add-Content "$rootPathprogress.txt" "********** Connecting to Graph API $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
$ApplicationID = "ApplicationID"
$TenatDomainName = "domain.com"
$AccessSecret = "ItsASecret"
$global:Body = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $ApplicationID
Client_Secret = $AccessSecret
}
$ConnectGraph = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenatDomainName/oauth2/v2.0/token" -Method POST -Body $Body
Add-Content "$rootPathprogress.txt" "********** Finished Connecting to Graph API $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
$global:token = $ConnectGraph.access_token
$global:UPN = "user@domain.com"
$global:AccessToken = $token
$global:User = $NULL
$global:Contact = $NULL
$global:NeedsToBeAdded = $NULL
$global:NeedsToBeDeleted = $NULL
$global:folderId = $NULL
$global:NewContactFolder = $NULL
$global:FolderName = "Test Contacts"
$global:headers = @{
"Authorization" = "Bearer $AccessToken"
"Accept" = "application/json;odata.metadata=none"
"Content-Type" = "application/json; charset=utf-8"
"ConsistencyLevel" = "eventual"
}
#Create Contact Folder if it doesn't exist
$global:ContactsFolderBody = @"
{
"parentFolderId": "$ParentFolderID",
"displayName": "Test Contacts"
}
"@
Add-Content "$rootPathprogress.txt" "********** Grabbing Contact Folder Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
$global:folders = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/$UPN/contactFolders" -Headers $headers
$global:ParentFolderID = $folders[0].value.parentFolderId
#Get Folder ID we are working with
foreach($folder in $folders.value)
{
#Reset the Value
$folderId = $NULL
if($FolderName -eq $folder.displayName)
{
$folderId = $folder.id
break
}
}
Add-Content "$rootPathprogress.txt" "********** Finished Grabbing Contact Folder Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
##################### Check if our Contacts Folder exists. If it doesn't, create it. #####################
if($NULL -eq $folderId)
{
Add-Content "$rootPathprogress.txt" "********** Creating Contact Folder $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
$Start = Get-Date
Write-Host "Creating Contacts Folder"
Try
{
while($NULL = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/$UPN/contactFolders/$folderId" -Headers $headers -Method get))
{
$NewContactFolder = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/$UPN/contactFolders" -Body $ContactsFolderBody -Headers $headers -Method post -ContentType 'application/json'
sleep -Milliseconds 1
$folderId = $($NewContactFolder.id)
}
}
Catch
{
Out-Null
}
$End = Get-Date
Write-Host "Contacts Folder created in $($Start - $End) seconds"
Write-Host ""
Add-Content "$rootPathprogress.txt" "********** Finished Creating Contact Folder $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
}
##################### Grab all of our User Information from AD #####################
Add-Content "$rootPathprogress.txt" "********** Grabbing AD User Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
$Start = Get-Date
$searcher=[adsisearcher]""
$searcher.Sort.PropertyName = "sn"
$searcher.Filter = "(&(objectcategory=person)(objectclass=user)(extensionAttribute2=custom)(|(mobile=*)(telephonenumber=*)))"
$colProplist = @(
'givenname', 'extensionattribute2'
'initials', 'mobile', 'telephonenumber'
'sn', 'displayname', 'company'
'title', 'mail', 'department'
'thumbnailphoto', 'samaccountname'
)
$colPropList | & { process {
$NULL = $searcher.PropertiesToLoad.Add($_)
}}
$End = Get-Date
Write-Host "User info took $($Start - $End) seconds"
Write-Host ""
Add-Content "$rootPathprogress.txt" "********** Finished Grabbing AD User Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
##################### Create our User Hashtable #####################
Add-Content "$rootPathprogress.txt" "********** Creating User Hashtable $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
Write-Host "Creating User Hashtable"
$Start = Get-Date
$users = $searcher.FindAll() | & { process {
[pscustomobject]@{
FirstName = [string]$_.properties.givenname
MiddleName = [string]$_.properties.initials
LastName = [string]$_.properties.sn
DisplayName = [string]$_.properties.displayname
SamAccountName = [string]$_.properties.samaccountname
Email = [string]$_.properties.mail
Mobile = [string]$_.properties.mobile
TelephoneNumber = [string]$_.properties.telephonenumber
Title = [string]$_.properties.title
Dept = [string]$_.properties.department
Company = [string]$_.properties.company
Photo = [string]$_.properties.thumbnailphoto
ExtensionAttribute2 = [string]$_.properties.extensionattribute2
}
}}
Write-Host "User Hashtable took $($Start - $End) seconds"
Write-Host ""
Add-Content "$rootPathprogress.txt" "********** Finished Creating User Hashtable $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
##################### Get Existing Contacts (Only if the Contacts Folder wasn't newly created )#####################
if($NULL -ne $folderId)
{
Add-Content "$rootPathprogress.txt" "********** Grabbing Contact Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
$Start = Get-Date
$AllContacts = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/$UPN/contactFolders/$folderId/contacts?`$top=999&`$Orderby=Surname" -Headers $headers -Method Get
$End = Get-Date
Write-Host "Contact info took $($Start - $End) seconds"
Write-Host ""
Add-Content "$rootPathprogress.txt" "********** Finished Grabbing Contact Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
##################### Create our Contact Hashtable #####################
Add-Content "$rootPathprogress.txt" "********** Creating Contact Hashtable $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
Write-Host "Creating Contact Hashtable"
$Start = Get-Date
$Contacts = $AllContacts.value | & { process {
[PSCustomObject]@{
'FirstName' = [string]$_.givenName
'MiddleName' = [string]$_.initials
'LastName' = [string]$_.surname
'DisplayName' = [string]$_.displayName
'Email' = [string](($_.emailAddresses) | %{$_.Address})
'Mobile' = [string]$_.mobilePhone
'TelephoneNumber' = [string]$_.homePhones
'Title' = [string]$_.jobTitle
'Dept' = [string]$_.department
'Company' = [string]$_.companyName
}
}}
$End = Get-Date
Write-Host "Contact HashTable took $($Start - $End) seconds"
Write-Host ""
Add-Content "$rootPathprogress.txt" "********** Finished Creating Contact Hashtable $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
}
##################### Start Comparing Data #####################
#Our Array of values we will be comparing
[array]$CompareValues = "FirstName","MiddleName","LastName","DisplayName","Email","Mobile","TelephoneNumber","Title","Dept","Company"
for($i=0; $i -lt $CompareValues.Count; $i++)
{
#First let's create 2 variables that will hold the info we want
$A = ($Users).($CompareValues[$i])
$B = ($Contacts).($CompareValues[$i])
##################### Update Contacts #####################
#Only Run if there are contacts; otherwise there is nothing for us to compare
if(($NULL -ne $B))
{
#Displays all differences
#$Differences = [string[]]([Linq.Enumerable]::Except([object[]]$a, [object[]]$b) + [Linq.Enumerable]::Except([object[]]$b, [object[]]$a))
#Displays what accounts we need to import
$NeedsToBeAdded = [string[]]([Linq.Enumerable]::Except([object[]]$a, [object[]]$b))
#Displays what accounts we need to delete because they no longer exist
$NeedsToBeDeleted = [string[]]([Linq.Enumerable]::Except([object[]]$b, [object[]]$a))
}
##################### Import All Contacts #####################
Else
{
Add-Content "$rootPathprogress.txt" "********** Importing All Contacts $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
$Start = Get-Date
<#
#---------------------------------------------
. $rootPathUpdateContactsUpdateContacts.ps1
# Set up runspace pool
$RunspacePool = [runspacefactory]::CreateRunspacePool(1,10)
$RunspacePool.Open()
# Assign new jobs/runspaces to a variable
$Runspaces = foreach ($User in $Users)
{
# Create new PowerShell instance to hold the code to execute, add arguments
$PSInstance = [powershell]::Create().AddScript({
$Users | ForEach{ UpdateContact($_) }
}).AddParameter('$_')
# Assing PowerShell instance to RunspacePool
$PSInstance.RunspacePool = $RunspacePool
# Start executing asynchronously, keep instance + IAsyncResult objects
New-Object psobject -Property @{
Instance = $PSInstance
IAResult = $PSInstance.BeginInvoke()
Argument = $User
}
}
# Wait for the the runspace jobs to complete
while($Runspaces |Where-Object{-not $_.IAResult.IsCompleted})
{
Start-Sleep -Milliseconds 500
}
# Collect the results
$Results = $Runspaces |ForEach-Object {
$Output = $_.Instance.EndInvoke($_.IAResult)
New-Object psobject -Property @{
User = $User
}
}
#---------------------------------------------
#>
Write-host "Importing Contacts. This could take several minutes."
#There are no contacts, so let's import them
#Path to our script that imports Contacts
. $rootPathUpdateContactsUpdateContacts.ps1
#$Users | & { process { UpdateContacts($_) } }
#Start-ThreadJob -ScriptBlock { $Users | & { process { UpdateContacts($_) } } }
Start-ThreadJob -ScriptBlock { $Users | ForEach{ UpdateContacts($_) } }
Get-Job
$End = Get-Date
Write-Host "Contact Import took $($Start - $End) seconds"
Write-Host ""
Add-Content "$rootPathprogress.txt" "********** Finished Importing All Contacts $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
break
}
}
Add-Content "$rootPathprogress.txt" "********** Finished Script $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPathprogress.txt" ""
有一堆代码需要处理,所以我将为您提供一个如何使用ThreadJob
在$users
中处理所有用户的蓝图。
因此,我会一步一步地添加我认为合适的评论,以引导您完成思考过程。
我不确定函数的输出是什么,因为我在Invoke-RestMethod
的末尾看到了一个| Out-Null
。你需要澄清这一点。
# requires -Modules ThreadJob
# Load UpdateContacts function in memory
. "$rootPathUpdateContactsUpdateContacts.ps1"
# Save the function in a scriptBlock, we need this
# so we can pass this function in the scope of the ThreadJobs
$updateContacts = "function UpdateContacts { $function:updateContacts }"
# Define the Number of Threads we are going to use
# (Get-CimInstance win32_processor).NumberOfLogicalProcessors
# Can give you a good perspective as to how many Threads is safe to use.
$numberOfThreads = 10
# I'm assuming that $users is the array we want to process with
# the UpdateContacts function. Around 500 as you said in your question.
# Here I'm grouping the users in chunks so each running Job can process
# a chunk of users. Each chunk will contain around 50 users to process.
$groupSize = [math]::Ceiling($users.Count / $numberOfThreads)
$counter = [pscustomobject]@{ Value = 0 }
$chunks = $users | Group-Object -Property {
[math]::Floor($counter.Value++ / $groupSize)
}
# Here is the magic
foreach($chunk in $chunks)
{
# Capture this chunk of users in a variable
$thisGroup = $chunk.Group
# This is what we are running inside the scope
# of our threadJob
$scriptBlock = {
# As in my comments, these variables don't exist inside here,
# you need to pass them to these scope
$UPN = $using:UPN
$folderID = $using:folderId
$headers = $using:headers
$contactsBody = $using:contactsBody
# First we need to define the function inside
# this scope
. ([scriptBlock]::Create($using:updateContacts))
# Loop through each user
foreach($user in $using:thisGroup)
{
UpdateContacts -User $user
}
} # EOF Job's ScriptBlock
# ThrottleLimit is the number of Jobs that can run at the same time.
# Be aware, a higher number of Jobs running does NOT mean that the
# task will perform faster. This always depends on your CPU & Memory.
# And, this case in particular, the number of requests your URI is able to handle
Start-ThreadJob -ScriptBlock $scriptBlock -ThrottleLimit $numberOfThreads
}
# Now we should have 10 Jobs running at the same time, each Job
# is processing a chunk of 50 users aprox. (500 users / 10)
# Note: As in my previous comments, I see an Out-Null in your function
# not sure what is meant to return but in case, this is how you capture
# the output of all Jobs:
$result = Get-Job | Receive-Job -Wait
# Free up memory:
Get-Job | Remove-Job