I am trying to fetch Recent file from sharepoint which is updated every month. Instead of manually downloading the file in local, I want to directly fetch from sharepoint and do analysis.
My sharepoint url looks likes this:
https://abc.sharepoint.com/_layouts/15/sharepoint.aspx
Where abc is my organisation name like apple, wipro so on.
I tried following this link:
Python - Download files from SharePoint site
## Sharepoint access
## pip install Office365-REST-Python-Client
from office365.runtime.auth.authentication_context import AuthenticationContext
from office365.sharepoint.client_context import ClientContext
ctx_auth = AuthenticationContext("https://abc.sharepoint.com/_layouts/15/sharepoint.aspx")
ctx_auth.acquire_token_for_user('**#abc.com', '***')
I get the error as:
An error occurred while retrieving auth cookies from
https://abc.sharepoint.com/_layouts/15/sharepoint.aspx/_vti_bin/idcrl.svc
False
Secondly, someone has shared me the link to sharepoint and I have access to download it.
It looks like:
https://abc-my.sharepoint.com/personal/persons_name_abc_com/_layouts/15/onedrive.aspx?view=4
Inside this there is folder named Analysis Files. In Analysis Files folder there is excel file which
I have to download.
How can I do it using python 3?
SharePoint App-Only is the older, but still very relevant, model of setting up app-principals. This model works for both SharePoint Online and SharePoint 2013/2016 on-premises and is ideal to prepare your applications for migration from SharePoint on-premises to SharePoint Online.
I am not familiar with Python3 but you can use the below code sample in your code as it uses REST API.
Please refer the following articles and let me know if you have any questions
SharePoint App-Only - https://sprider.blog/spol-download-file-from-library-using-sharepoint-app-only-access-and-powershell
Graph API - https://sprider.blog/download-sharepoint-file-using-microsoft-graph-api
How to get Tenant ID - https://sprider.blog/get-office-365-tenant-id-from-domain-name
Tenant Name - abc as per your code example.
Client Id and Secret can be retrived from SharePoint or Azure AD App Registration
###### Global Static Variables - Start ######
$grantType = "client_credentials"
$principal = "00000003-0000-0ff1-ce00-000000000000"
###### Global Static Variables - End ######
###### Global Tenant Specific Variables - Start ######
$m365TenantId = "yourtenantguid"
$targetHost = "yourtenantname.sharepoint.com"
$appClientId = "clientid-from-previous-section"
$appClientSecret = "clientsecret-from-previous-section"
###### Global Tenant Specific Variables - End ######
###### Site/File Path Variables - Start ######
$targetFolder = $PSScriptRoot
$siteRelativeUrl = "sites/yoursite"
$folderRelativeUrl = "your-document-library-name"
$fileName = "your-file-name.png"
###### Site/File Path Variables - Start ######
###### Helper Functions - Start ######
function Add-Working-Directory([string]$workingDir, [string]$logDir) {
if (!(Test-Path -Path $workingDir)) {
try {
$suppressOutput = New-Item -ItemType Directory -Path $workingDir -Force -ErrorAction Stop
$msg = "SUCCESS: Folder '$($workingDir)' for CSV files has been created."
Write-Host -ForegroundColor Green $msg
}
catch {
$msg = "ERROR: Failed to create '$($workingDir)'. Script will abort."
Write-Host -ForegroundColor Red $msg
Exit
}
}
if (!(Test-Path -Path $logDir)) {
try {
$suppressOutput = New-Item -ItemType Directory -Path $logDir -Force -ErrorAction Stop
$msg = "SUCCESS: Folder '$($logDir)' for log files has been created."
Write-Host -ForegroundColor Green $msg
}
catch {
$msg = "ERROR: Failed to create log directory '$($logDir)'. Script will abort."
Write-Host -ForegroundColor Red $msg
Exit
}
}
}
function Add-Log([string]$message, [string]$logFile) {
$lineItem = "[$(Get-Date -Format "dd-MMM-yyyy HH:mm:ss") | PID:$($pid) | $($env:username) ] " + $message
Add-Content -Path $logFile $lineItem
}
function Get-AccessToken {
try {
$message = "Getting Accesstoken..."
Add-Log $message $Global:logFile
$tokenEndPoint = "https://accounts.accesscontrol.windows.net/$m365TenantId/tokens/oauth/2"
$client_Id = "$appClientId#$m365TenantId"
$resource = "$principal/$targetHost#$m365TenantId"
$requestHeaders = #{
"Content-Type" = "application/x-www-form-urlencoded"
}
$requestBody = #{
client_id = $client_Id
client_secret = $appClientSecret
grant_type = $grantType
resource = $resource
}
$response = Invoke-RestMethod -Method 'Post' -Uri $tokenEndPoint -Headers $requestHeaders -Body $requestBody
$accesstoken = $response.access_token
$message = "Accesstoken received."
Add-Log $message $Global:logFile
return $accesstoken
}
catch {
$statusCode = $_.Exception.Response.StatusCode.value__
$statusDescription = $_.Exception.Response.StatusDescription
$message = "StatusCode: $statusCode"
Add-Log $message $Global:logFile
$message = "StatusDescription : $statusDescription"
Add-Log $message $Global:logFile
return $null
}
}
function Download-File([string]$fileUrl, [string]$targetFilePath) {
$accessToken = Get-AccessToken
if (![string]::IsNullOrEmpty($accessToken)) {
try {
$fileUri = New-Object System.Uri($fileUrl)
$wc = New-Object System.Net.WebClient
$wc.Headers.Add("Authorization", "Bearer $accessToken")
$job = $wc.DownloadFileTaskAsync($fileUri, $targetFilePath)
$message = "Downloading file $fileUrl at $targetFilePath."
Add-Log $message $Global:logFile
while (!$job.IsCompleted) {
sleep 1
}
if ($job.Status -ne "RanToCompletion") {
$message = "Failed to download file."
Add-Log $message $Global:logFile
}
else {
$message = "File downloaded."
Add-Log $message $Global:logFile
}
}
catch {
$statusCode = $_.Exception.Response.StatusCode.value__
$statusDescription = $_.Exception.Response.StatusDescription
$message = "StatusCode: $statusCode"
Add-Log $message $Global:logFile
$message = "StatusDescription : $statusDescription"
Add-Log $message $Global:logFile
$message = "Failed to download file."
Add-Log $message $Global:logFile
}
}
else {
$message = "Unable to get Accesstoken."
Add-Log $message $Global:logFile
}
}
###### Helper Functions - End ######
###### Main Program - Start ######
###### Log Setup - Start ######
$currentDirectoryPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$workingDirectory = $currentDirectoryPath
$logDirectoryName = "Logs"
$logDirectory = "$workingDirectory/$logDirectoryName"
$logFileName = "$(Get-Date -Format "yyyyMMddTHHmmss")_downloadjobexecution.log"
$Global:logFile = "$logDirectory/$logFileName"
Add-Working-Directory $workingDirectory $logDirectory
###### Log Setup - Start ######
Write-Host -ForegroundColor Yellow "WARNING: Minimal output will appear on the screen."
Write-Host -ForegroundColor Yellow " Please look at the log file '$($logFile)'"
$message = "**************************************** SCRIPT STARTED ****************************************"
Add-Log $message $Global:logFile
###### Download File - Start ######
$targetFilePath = Join-Path $targetFolder $fileName
$fileUrl = "https://$targetHost/$siteRelativeUrl/_api/Web/GetFolderByServerRelativeUrl('$folderRelativeUrl')/Files('$fileName')/`$value"
Download-File $fileUrl $targetFilePath
###### Download File - End ######
$message = "**************************************** SCRIPT COMPLETED ****************************************"
Add-Log $message $Global:logFile
###### Main Program - End ######
The error shows that there is something wrong when you get authorization. You could connect to SharePoint site like below:
tenant_url= "https://{tenant}.sharepoint.com"
ctx_auth = AuthenticationContext(tenant_url)
site_url="https://{tenant}.sharepoint.com/sites/{yoursite}"
ctx_auth.acquire_token_for_user("username","password"):
Related
I am trying to work on an existing script that I had some assistance from in another thread. With some member assistance I was able to get my script to run using the "ThreadJob" module, however I was hoping I can also make use of runspacepools in conjunction with the ThreadJob to make it run faster.
In my code I post below, I am printing out a line to notify me that the function get's called. And I can see it is getting called. So it makes me think that line 56 and line 59 are incorrectly being called and I can't figure out how to call them.
if I run the "$rootPath\UpdateContacts\UpdateContacts.ps1" file manually through powershell ISE, it runs fine (obviously outside of the runspace), but I'd like to try and get it to work within the runspacepool.
Here is what I'm working with. I think I am pretty close.
begin
{
CLS
[switch]$MultiThread=$true
$rootPath = $(Split-path $MyInvocation.MyCommand.path -Parent)
$userEmail = "user#domain.com"
$SessionState = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, 10,$Sessionstate, $Host)
$RunspacePool.Open()
$Jobs = #()
}
process
{
function ContactUpdater()
{
##################### 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 #####################
if($NULL -eq $Contacts)
{
Write-Host "I am in the import"
# Load UpdateContacts function in memory
. "$rootPath\UpdateContacts\UpdateContacts\UpdateContacts.ps1"
#Write-host "Importing Contacts. This could take several minutes."
& "$rootPath\UpdateContacts\UpdateContacts.ps1"
}
}
if($MultiThread)
{
foreach($userEmail in $EmailAddress)
{
try
{
##################### Create Contact Folder #####################
if($NULL -eq $folderId)
{
$start = [datetime]::UtcNow
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
}
Add-Content "$rootPath\progress.txt" "`t`tCreated Contacts Folder in: $('{0:N2}' -f ([datetime]::UtcNow - $start).TotalSeconds) seconds"
Add-Content "$rootPath\progress.txt" ""
}
##################### Getting All User Info #####################
$start = [datetime]::UtcNow
$Users = & $rootPath\GetUserInfo\GetUserInfo.ps1
Add-Content "$rootPath\progress.txt" "`t`tFinished Getting all User Info in: $('{0:N2}' -f ([datetime]::UtcNow - $start).TotalSeconds) seconds"
Add-Content "$rootPath\progress.txt" ""
##################### Getting Contact Info #####################
if($NULL -ne $folderId)
{
$start = [datetime]::UtcNow
$Contacts = & $rootPath\GetContactInfo\GetContactInfo.ps1
Add-Content "$rootPath\progress.txt" "`t`tFinished Getting all Contact Info in: $('{0:N2}' -f ([datetime]::UtcNow - $start).TotalSeconds) seconds"
Add-Content "$rootPath\progress.txt" ""
}
##################### Import Contacts #####################
$start = [datetime]::UtcNow
CLS
if($NULL -eq $ImportMsg)
{
Write-host "Importing Contacts. This could take several minutes."
$ImportMsg = "Ran"
}
$ContactImporter = ContactUpdater
Add-Content "$rootPath\progress.txt" "`t`tFinished Importing Contact Info in: $('{0:N2}' -f ([datetime]::UtcNow - $start).TotalSeconds) seconds"
}
catch
{
$LogFile = "$rootPath\log.txt"
$errcond = $_.Exception.Message
$timestamp = (get-date).DateTime
"Time of exception: $timestamp" | Out-File $LogFile -Append
"User: $userEmail" | out-file $LogFile -Append
$errcond | out-file -FilePath $LogFile -append
}
1..10 | Foreach-Object {
$PowershellThread = [powershell]::Create()
$PowershellThread.RunspacePool = $RunspacePool
$PowershellThread.AddScript($ContactImporter).AddArgument($userEmail)
$Jobs += $PowershellThread.BeginInvoke()
}
}
}
}
end
{
if($MultiThread)
{
while ($Jobs.IsCompleted -contains $false)
{
Start-Sleep -Milliseconds 100
}
$RunspacePool.Close() | Out-Null
$RunspacePool.Dispose() | Out-Null
}
}
The part in the "Import all Contacts" section within the ContactUpdater() Function, should call the script:
& "$rootPath\UpdateContacts\UpdateContacts.ps1"
That script looks like this:
# 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
# $users is the array we want to process with
# the UpdateContacts function.
# 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)
}
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 = {
# Pass our variables to this scope
$UPN = $using:UPN
$folderID = $using:folderId
$headers = $using:headers
$contactsBody = $using:contactsBody
$ImportMsg = $using:ImportMsg
# 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
}
}
# 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)
# the output of all Jobs:
$result = Get-Job | Receive-Job -Wait
# Free up memory:
Get-Job | Remove-Job
That code above, starts a threadjob and launches another function in "$rootPath\UpdateContacts\UpdateContacts\UpdateContacts.ps1"
And that script looks like this:
Function UpdateContacts($User)
{
#FirstName, MiddleName, LastName, DisplayName, SamAccountName, Email, Mobile, TelephoneNumber, Title, Dept, Company, Photo, ExtensionAttribute2
$ContactsBody = #"
{
"givenName" : "$($User.FirstName)",
"middleName" : "$($User.MiddleName)",
"surname" : "$($User.LastName)",
"fileAs" : "$($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
}
}
I'm trying to import contacts using RunspacePools, but I'm having trouble getting it to work. If I take it out of the runspace logic, it works fine, just takes a long time. I'd really like to use runspacepools to speed up the import process and make it run multithreaded so it imports faster. On avg each import takes about 5-6 mins per user, and I have about 500 users, so it can take up to 3000 mins to run.
Here is what I currently have:
#---------------------------------------------
. $rootPath\UpdateContacts\UpdateContacts.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
}
}
And my "UpdateContacts.ps1" file looks like this:
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
}
}
Any help is appreciated.
EDIT:
Here is the full script (with the exception of the ContactUploader.ps1 script. That function is in a separate script but the whole code (Function) is posted above).
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 "$rootPath\progress.txt" ""
Add-Content "$rootPath\progress.txt" "********** Starting Script $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.txt" ""
##################### Connect to Microsoft Graph API and configure all of our Variables #####################
Add-Content "$rootPath\progress.txt" "********** Connecting to Graph API $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.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 "$rootPath\progress.txt" "********** Finished Connecting to Graph API $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.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 "$rootPath\progress.txt" "********** Grabbing Contact Folder Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.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 "$rootPath\progress.txt" "********** Finished Grabbing Contact Folder Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.txt" ""
##################### Check if our Contacts Folder exists. If it doesn't, create it. #####################
if($NULL -eq $folderId)
{
Add-Content "$rootPath\progress.txt" "********** Creating Contact Folder $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.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 "$rootPath\progress.txt" "********** Finished Creating Contact Folder $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.txt" ""
}
##################### Grab all of our User Information from AD #####################
Add-Content "$rootPath\progress.txt" "********** Grabbing AD User Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.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 "$rootPath\progress.txt" "********** Finished Grabbing AD User Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.txt" ""
##################### Create our User Hashtable #####################
Add-Content "$rootPath\progress.txt" "********** Creating User Hashtable $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.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 "$rootPath\progress.txt" "********** Finished Creating User Hashtable $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.txt" ""
##################### Get Existing Contacts (Only if the Contacts Folder wasn't newly created )#####################
if($NULL -ne $folderId)
{
Add-Content "$rootPath\progress.txt" "********** Grabbing Contact Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.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 "$rootPath\progress.txt" "********** Finished Grabbing Contact Info $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.txt" ""
##################### Create our Contact Hashtable #####################
Add-Content "$rootPath\progress.txt" "********** Creating Contact Hashtable $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.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 "$rootPath\progress.txt" "********** Finished Creating Contact Hashtable $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.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 "$rootPath\progress.txt" "********** Importing All Contacts $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.txt" ""
$Start = Get-Date
<#
#---------------------------------------------
. $rootPath\UpdateContacts\UpdateContacts.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
. $rootPath\UpdateContacts\UpdateContacts.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 "$rootPath\progress.txt" "********** Finished Importing All Contacts $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.txt" ""
break
}
}
Add-Content "$rootPath\progress.txt" "********** Finished Script $(Get-Date -Format "HH:mm ss")**********"
Add-Content "$rootPath\progress.txt" ""
There is a bunch of code to go through so I'm gonna give you a blueprint of how you can achieve processing all users in $users using ThreadJob.
So, step by step, I'll try to add as much comments as I consider appropriate to guide you through the thought process.
I'm not sure what is the output of your function since I see an | Out-Null at the end of the Invoke-RestMethod. You would need to clarify on this.
# requires -Modules ThreadJob
# Load UpdateContacts function in memory
. "$rootPath\UpdateContacts\UpdateContacts.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
Hi I am trying to upload published files from Azure Git artifacts to FTP. But randomly I am getting the below error.
This is only happening for files not for any folder or subfolders.
“UploadFile” with “2” argument(s): “The Content-Type header cannot be set to a multipart type for this request.
All the files are available in the artifacts.
Observation & tried:
All files are present in artifacts
Getting the error only for files. (Folder & subfolders are creating successfully)
Stopped the Web App and then tried to upload
Tried Sleep also between uploading two files
After all of these, the result is the same.
Can anyone please help me out?
Get-ChildItem -Path $target_directory
# Upload files recursively
write-host "target_directory - $target_directory"
Set-Location $target_directory
$webclient = New-Object -TypeName System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($username,$password)
$files = Get-ChildItem -Path $target_directory -Recurse
write-host 'Uploading started............................'
foreach ($file in $files)
{
write-host "file -" + $file.FullName
if($file.FullName -match "web.config" -or $file.FullName -match ".pdb" -or $file.FullName -match "roslyn" -or $file.FullName -match "obj\Debug"){
write-host "ignoring " + $file.FullName
continue
}
$relativepath = (Resolve-Path -Path $file.FullName -Relative).Replace(".\", "").Replace('\', '/')
$uri = New-Object System.Uri("$url/$relativepath")
write-host "uri -" + $uri.AbsoluteUri
write-host '--------'
if($file.PSIsContainer)
{
write-host 'PSIsContainer - All dir/files within' $file
get-childitem -path $file -Recurse
try
{
$makeDirectory = [System.Net.WebRequest]::Create($uri);
$makeDirectory.Credentials = New-Object System.Net.NetworkCredential($username,$password);
$makeDirectory.Method = [System.Net.WebRequestMethods+FTP]::MakeDirectory;
$makeDirectory.GetResponse();
#folder created successfully
}
catch [Net.WebException]
{
try
{
#if there was an error returned, check if folder already existed on server
$checkDirectory = [System.Net.WebRequest]::Create($uri);
$checkDirectory.Credentials = New-Object System.Net.NetworkCredential($username,$password);
$checkDirectory.Method = [System.Net.WebRequestMethods+FTP]::PrintWorkingDirectory;
$response = $checkDirectory.GetResponse();
$response.StatusDescription
#folder already exists!
}
catch [Net.WebException]
{
#if the folder didn't exist, then it's probably a file perms issue, incorrect credentials, dodgy server name etc
}
}
continue
}
try{
write-host "Uploading to " $uri.AbsoluteUri " from " $file.FullName
$webclient.UploadFile($uri, $file.FullName)
start-sleep -Seconds 1
}
catch{
##[error]Error message
Write-Host "##[error]Error in uploading " $file -ForegroundColor red
Write-Host $_
$disputedFiles = $disputedFiles + $file.FullName
$_file = #{
FileName = $file.FullName
FTPPath = $uri.AbsoluteUri
}
$_o = New-Object psobject -Property $_file;
$disputedFilesList = $disputedFilesList + $_o
}
}
Write-Host "##[debug] Starting uploading the disputed files....."
foreach($file in $disputedFilesList){
try{
write-host "Uploading to " $file.FTPPath " from " $file.FileName
$webclient.UploadFile($file.FTPPath, $file.FileName)
start-sleep -Seconds 1
}
catch{
write-host "##[error]Error(2) in uploading to " $file.FTPPath " from " $file.FileName
}
}
Write-Host "##[debug] Ending uploading the disputed files....."
remove-item -path $target_directory\* -Recurse
get-childitem -path $target_directory
write-host "Directory Empty after cleanup"
$webclient.Dispose()
I am getting this error from the following code. It's coming from $Context.Load($RecycleBinItems). Any idea what's wrong with the code? I am attempting to restore all recyclebin items.
Add-Type -Path "C:\Program Files\WindowsPowerShell\Modules\SharePointPnPPowerShellOnline\3.17.2001.2\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\Program Files\WindowsPowerShell\Modules\SharePointPnPPowerShellOnline\3.17.2001.2\Microsoft.SharePoint.Client.Runtime.dll"
Import-Module 'Microsoft.PowerShell.Security'
#Get the Site Owners Credentials to connect the SharePoint
$SiteUrl = "https://phaselinknet.sharepoint.com"
$UserName = Read-host "Enter the Email ID"
$Password = Read-host - assecurestring "Enter Password for $AdminUserName"
$Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName, $Password)
# Once Connected, get the Site information using current Context objects
Try {
$Context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
$Context.Credentials = $Credentials
$Site = $Context.Site
$RecycleBinItems = $Site.RecycleBin
$Context.Load($Site)
$Context.Load($RecycleBinItems)
$Context.ExecuteQuery()
Write-Host "Total Number of Files found in Recycle Bin:" $RecycleBinItems.Count
}
catch {
write - host "Error: $($_.Exception.Message)" - foregroundcolor Red
}
# using for loop to restore the item one by one
Try {
if($RecycleBinItems)
{
foreach($Item in $RecycleBinItems)
{
$Site.RecycleBin.restore($Item.ID)
#Write-Host "Item restored:"$Item.Title
}
}
}
catch {
write-host "Error: $($_.Exception.Message)" -foregroundcolor Red
}
The error message is giving you you answer. There is not a version of the method Restore that takes 1 parameter.
You need to load up a list of items simular to this
$Item = $RecycleBin | Where{$_.Title -eq $ItemName}
Then call restore for the items.
if($Item -ne $null)
{
$Item.Restore()
}
Thanks for the tip. So I load up the first 10 items in the recyclebin, and Write-Host does write out the correct files, but the $Item.Restore() does noting as the files are still not restored:
$itemsToRestore = #()
for ($i = 0; $i -lt 10; $i++)
{
$Item = $RecycleBinItems[$i]
$itemsToRestore += $Item
}
Write-Host "Total Number of Files to Restore:" $itemsToRestore.Count
foreach($item in $itemsToRestore)
{
Write-Host "Item:" $Item.Title
$item.Restore()
}
I found the problem. I missed $Context.ExecuteQuery() after $Item.Restore(). It works now.
I have a function in a script that I run to make some changes to a web.config file in Azure on one of our subscriptions. It modifies an endpoint address based on searching for keywords. It works fine. I need to add into it to also modify the another key in the web.config but not 100% sure how I will add it to this function. Here is the function code:
function Update-ServiceConfigFile() {
Write-Verbose "Updating Service Web Config"
$webConfig = 'directoryToConfigInAzure/web.config'
Write-Verbose -Message "Downloading $webConfig"
$tempFile = [IO.Path]::GetTempFileName()
Write-Verbose "Write $tempFile"
Download-File $accessToken $webappName $webConfig $tempFile
$doc = [xml](Get-Content $tempFile)
$customerId = $WebAppName.Substring(0, 6)
$nodes = $doc.configuration.'system.serviceModel'.client.endpoint
foreach($node in $nodes) {
$address = $node.Attributes['address'].Value;
$address = $address -replace ':ServicePortNumber', ''
$ub = [System.UriBuilder]::new($address);
if($Insecure) {
Write-Verbose "Setting HTTP (insecure) API endpoint"
$ub.Scheme = 'http';
} else {
Write-Verbose "Setting HTTPS (secure) API endpoint"
$ub.Scheme = 'https';
}
if($address.contains("SomeAddress"))
{
$ub.Host = "service-prod-$($customerId).ourdomainname.cloud"
}else{
$ub.Host = "service-$($customerId).ourdomnaineame.cloud";
}
if($webAppName -like '*-test') {
$ub.Host = "service-test-$($customerId).ourdomnaineame.cloud";
}
$ub.Port = -1;
$node.Attributes['address'].Value = $ub.Uri.AbsoluteUri;
}
$doc.Save($tempFile)
Upload-File $accessToken $webappName $webConfig $tempFile
Remove-Item $tempFile -Force
}
What I need to add in is to change another value in the system.serviceModel security mode from "none" to "transport"
You can update the security mode for all binding nodes with code like this. It will work even if you have multiple binding nodes and different kind of binding nodes (like wsHttpBinding, basicHttpBinding etc.)
$bindingNodes = $doc.configuration.'system.serviceModel'.bindings
$securityNodes = $bindingNodes.SelectNodes("//security")
foreach($securityNode in $securityNodes)
{
$mode = $securityNode.Attributes['mode'].Value;
Write-Host("Mode = ", $mode)
# I am checking for none in a case insensitive way here, so only none will be changed to transport. You can remove the if condition, in case you want any value to change to Transport.
if($mode -ieq 'none') {
$securityNode.Attributes['mode'].Value = 'Transport'
}
}
I have given only the important part above for your understanding.
Overall function code would change to something like this..
function Update-ServiceConfigFile() {
Write-Verbose "Updating Service Web Config"
$webConfig = 'directoryToConfigInAzure/web.config'
Write-Verbose -Message "Downloading $webConfig"
$tempFile = [IO.Path]::GetTempFileName()
Write-Verbose "Write $tempFile"
Download-File $accessToken $webappName $webConfig $tempFile
$doc = [xml](Get-Content $tempFile)
$customerId = $WebAppName.Substring(0, 6)
$nodes = $doc.configuration.'system.serviceModel'.client.endpoint
foreach($node in $nodes) {
$address = $node.Attributes['address'].Value;
$address = $address -replace ':ServicePortNumber', ''
$ub = [System.UriBuilder]::new($address);
if($Insecure) {
Write-Verbose "Setting HTTP (insecure) API endpoint"
$ub.Scheme = 'http';
} else {
Write-Verbose "Setting HTTPS (secure) API endpoint"
$ub.Scheme = 'https';
}
if($address.contains("SomeAddress"))
{
$ub.Host = "service-prod-$($customerId).ourdomainname.cloud"
}else{
$ub.Host = "service-$($customerId).ourdomnaineame.cloud";
}
if($webAppName -like '*-test') {
$ub.Host = "service-test-$($customerId).ourdomnaineame.cloud";
}
$ub.Port = -1;
$node.Attributes['address'].Value = $ub.Uri.AbsoluteUri;
}
# NEW CODE STARTS HERE
$bindingNodes = $doc.configuration.'system.serviceModel'.bindings
$securityNodes = $bindingNodes.SelectNodes("//security")
foreach($securityNode in $securityNodes)
{
$mode = $securityNode.Attributes['mode'].Value;
Write-Host("Mode = ", $mode)
# I am checking for none in a case insensitive way here, so only none will be changed to transport. You can remove the if condition, in case you want any value to change to Transport.
if($mode -ieq 'none') {
$securityNode.Attributes['mode'].Value = 'Transport'
}
}
# NEW CODE ENDS HERE
$doc.Save($tempFile)
Upload-File $accessToken $webappName $webConfig $tempFile
Remove-Item $tempFile -Force
}