Connecting to Office 365 in PSjobs

This topic contains 4 replies, has 2 voices, and was last updated by  Rana Banerjee 1 week, 3 days ago.

  • Author
    Posts
  • #102196

    Rana Banerjee
    Participant

    Hello PowerShell Experts, I am trying to connect to Office 365 / Exchange Online in PSJobs, but it fails to connect to Office 365 in the PSjob. Please help.

    Many thanks

    Code below :

    $Credential = Get-Credential #Stored my credentials and Verified connecting directly. works OK
    $script = {
    param (
    [pscredential]$credential #tried without parameter type with no luck
    )
    $paramNewPSSession = @{
    ConfigurationName = 'Microsoft.Exchange'
    ConnectionUri = 'https://outlook.office365.com/powershell-liveid/'
    Credential = $Credential
    Authentication = 'Basic'
    AllowRedirection = $true
    }

    $Session = New-PSSession @paramNewPSSession
    Import-PSSession $Session -AllowClobber -DisableNameChecking
    Get-Mailbox #expecting this to be executed
    }

    $range = 1 .. 3
    foreach ($i in $range)
    {
    Start-Job -Name "job$i" -ScriptBlock $script -ArgumentList $credential
    }

  • #102214

    postanote
    Participant

    That whole connection to O365 via job notwithstanding. I have a few questions.

    Why you creating a credential object multiple times for the same use?
    You only need this once.

    Why are you trying to run an interactive O365 session as a series of jobs?
    You only need one session and since is interactive, it should not be a background job and

    In the jobs call, you are calling the same code block 3 times, why?
    You only need one, and calling it 3 times, is just proxying the same cmdlets (and running that Get-Mailbox command which just spits the results to the screen with allow clobber and no session prefix, last in wins and all your session names would be the same. The results will be the same for all jobs, even if it did work.

    What are you trying to accomplish by this, multiple job thing to the same end resource running the exact same command, thus just getting the exact same results, even if it did work?

    I am at a real loss as to why you choose this approach, hence the questions. Yet, based on what it appears that you are after, you need to rethink your goals here.

    As for the reason for the failure is that the $Session is never getting created. So, that splatting / job effort is not working.
    If you put Get-PSSession just before Get-Mailbox and or in after the Start-Job line, you'll see that as well.

    If you run that code blocks / and variables individually. you'll see what I mean.

    BTW, even if you removed the splatting effort and just coded it raw, you'd see the same non-connection, no session created thing.
    So, all-in-all, you can't do this, the way you are trying to, meaning, using this interactive session setup as a job.

  • #102220

    Rana Banerjee
    Participant

    @postanot, thanks for your reply.
    PLEASE READ MY QUESTION CAREFULLY, I ONLY REQUESTED TO HELP ME FIND A SOLUTION TO CONNECT TO OFFICE 365 WITHIN PSJOBS.
    Get-mailbox was only an example. the focus was clearly on the session. your reason for ranting was just bizarre. you further went on commenting on splatting and all other things which no one asked and I very well know how splatting works. All I needed was a solution to how to connect to office 365 in psjobs.

    If you really wanted to know then the reason is we carry out pre and post migrations tasks on 7000+ users in a batch and processing 7000+ users will take a VERY LONG TIME so I thought to split it up in smaller sets and run it. code below is an example.

    below example is for enabling litigation hold. (many other things we do similar to this for such large no of users)
    please see the example below for litigation hold.

    ONCE AGAIN THE FOCUS IS 'HOW TO CONNECT TO OFFICE 365 SESSION VIA PSJOBS'

    function Split-Array
    {
    	param (
    		$inArray,
    		[int]$parts,
    		[int]$size
    	)
    	if ($parts)
    	{
    		$PartSize = [Math]::Ceiling($inArray.count / $parts)
    	}
    	if ($size)
    	{
    		$PartSize = $size
    		$parts = [Math]::Ceiling($inArray.count / $size)
    	}
    	
    	$outArray = New-Object 'System.Collections.Generic.List[psobject]'
    	
    	for ($i = 1; $i -le $parts; $i++)
    	{
    		$start = (($i - 1) * $PartSize)
    		$end = (($i) * $PartSize) - 1
    		if ($end -ge $inArray.count) { $end = $inArray.count - 1 }
    		$outArray.Add(@($inArray[$start .. $end]))
    	}
    	return, $outArray
    }
    
    Function Enable-QHLitigationHold
    {
    	param (
    		[Parameter(Mandatory = $true,
    				   ValueFromPipeline = $true,
    				   ValueFromPipelineByPropertyName = $true,
    				   HelpMessage = 'Please enter UPN or EmailAddress')]
    		[ValidateNotNullOrEmpty()]
    		[Alias('EmailAddress', 'PrimarySmtpAddress')]
    		[String[]]$UserPrincipalName
    	)
    	
    	BEGIN
    	{
    		$i = 1
    		
    	}
    	PROCESS
    	{
    		foreach ($UPN in $UserPrincipalName)
    		{
    			Try
    			{
    				Set-Mailbox -identity $UPN -LitigationHoldEnabled $true -ErrorAction 'Stop'
    				$prop = [ordered] @{
    					User = $UPN
    					LitigationHold = 'Success'
    					Details = 'None'
    				}
    			}
    			Catch
    			{
    				$prop = [ordered] @{
    					User = $UPN
    					LitigationHold = 'Failed'
    					Details = "ERROR : $($_.Exception.Message)"
    				}
    			}
    			Finally
    			{
    				$obj = New-Object -TypeName System.Management.Automation.PSObject -Property $prop
    				Write-Output $obj
    				
    				if ($UserPrincipalName.count -gt 1)
    				{
    					$paramWriteProgress = @{
    						Activity = 'Enabling Litigation Hold'
    						Status = "Processing [$i] of [$($UserPrincipalName.Count)] users"
    						PercentComplete = (($i / $UserPrincipalName.Count) * 100)
    						CurrentOperation = "Completed : [$UPN]"
    					}
    					Write-Progress @paramWriteProgress
    					$i++
    				}
    			}
    		}
    	}
    	END
    	{
    		Write-Progress -Activity 'Enabling Litigation Hold' -Completed
    	}
    }
    
    # the above two functions are in a module O365OPS at c:\tools\O365Ops.psm1
    
    $credential = Get-credential #Credential object created.
    
    $scriptBlock = {
    	param (
    		$Users,
    		$credential,
    		$x
    	)
    	Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
    	Import-Module "c:\tools\O365Ops.psm1" -WarningAction SilentlyContinue
            #if I remove $credential parameter from the script block and use the credentials from a CLIXML it works. but I want to be able to use it as a parmeter
    	
    	$paramNewPSSession = @{
    		ConfigurationName = 'Microsoft.Exchange'
    		ConnectionUri = 'https://outlook.office365.com/powershell-liveid/'
    		Credential = $Credential
    		Authentication = 'Basic'
    		AllowRedirection = $true
    	}
    	
    	$Session = New-PSSession @paramNewPSSession
    	Import-PSSession $Session -AllowClobber -DisableNameChecking -Prefix EXO
    	Enable-QHLitigationHold -UserPrincipalName $Users | Export-Csv "LitigationHoldreport_$x.csv" -NoTypeInformation
    	
    }
    
    $dataset = Split-Array $userprincipalname -parts 3 # $userprincipalName has 6000+ users. This will split them into 3 equal parts
    
    $x = 1
    
    foreach ($set in $dataset)
    {
    	$users = $set
    	Start-Job -Name "job$x" -ScriptBlock $scriptBlock -ArgumentList $users, $credential, $x
    	$x++
    }
    
  • #102322

    postanote
    Participant

    Firstly, I don't rant (well, I do, but never on forums like this).
    However, if / when I do, I am clear to state that it is a rant by fully qualifying it like so.

    //begen rant
    ...yadd, yadda, yadda.
    //end rant

    Why, because I don't like abiguity in conversation to knowledge exchnages.

    However, unqualified rant is a non-sequitur and it's not productive for anyone at anytime. The bold was only separate the question for the comment.

    Folks using all caps, are ranting and yelling motifs as well. At least that is the take most have on the topic and as such, again, a non-sequitur.

    For me, even when folks to the all caps thing, I simply ignore it, as it happens for whatever reason they feel they need to do it. None the less, much as you feel mine was a rant, which it was not, the all caps thing is also not prudent to any productive conversation.

    Your second post is far more detailed, and things would have been far more clear with this one had it been your first post. So, all things being equal, all this angst is rather moot.

    As to your concern, this...
    "if I remove $credential parameter from the script block and use the credentials from a CLIXML it works. but I want to be able to use it as a parmeter"

    … is an odd thing for sure and I've seen/had this happen (multiple times) with straight calls to MSOL for different operational (flow) steps. Meaning, you set creds at one point but they do not carry to another. The CLIXML (stored creds and call them at each operational request where I've gotten the no creds thing happening) approach is what I have defaulted to for sometime because of this oddity. I've yet to figure out why the cred loss for inline script efforts. For now, the CLIXML does the job until I decided to dig at this more when I get time.

    So, it appares you've now run into this ghost as I have, and though you have an admitted workaround, similar to what I am doing, you consider it sub-optimal for your efforts.

    So, if it were me, based on what you show now. I'd remove all the connection stuff to it's own block or function (it's what I had to do in my use cases), call it once, and execute on the jobs segment . So, using your original shortened code block.

        
    $Credential = Get-Credential -Credential admin@domain.onmicrosoft.com'
    
    $paramNewPSSession = @{
    ConfigurationName = 'Microsoft.Exchange'
    ConnectionUri = 'https://outlook.office365.com/powershell-liveid/'
    Credential = $Credential
    Authentication = 'Basic'
    AllowRedirection = $true
    }
    
    $Session = New-PSSession @paramNewPSSession
    Import-PSSession $Session -AllowClobber -DisableNameChecking
    Get-PSSession
    
    $script = {Get-Mailbox}
    
    $range = 1 .. 3
    
    foreach ($i in $range)
    { Start-Job -Name "job$i" -ScriptBlock {Invoke-Command -session $session {$script}}}
    
    Start-Sleep -Seconds 9
    Get-Job | Format-Table -AutoSize
    
    # Results
    
    ModuleType Version    Name             ExportedCommands
    ---------- -------    ----             ----------------
    Script     1.0        tmp_nd3zuojn.jcf {Add-AvailabilityAddressSpace...}
    
    ...     : 
    
    
    ...
    Id            : 2
    Name          : job1
    ChildJobs     : {Job3}
    ... 
    PSJobTypeName : BackgroundJob
    ...
    State         : Running
    
    
    ...
    Id            : 4
    Name          : job2
    ChildJobs     : {Job5}
    ... 
    PSJobTypeName : BackgroundJob
    ...
    State         : Running
    
    
    ...
    Id            : 6
    Name          : job3
    ChildJobs     : {Job7}
    ... 
    PSJobTypeName : BackgroundJob
    ...
    State         : Running
    
    
    Start-Sleep -Seconds 9
    
    Get-Job | Format-Table -AutoSize
    
    Id Name PSJobTypeName State     HasMoreData Location  Command                                   
    -- ---- ------------- -----     ----------- --------  -------                                   
    2  job1 BackgroundJob Completed True        localhost Invoke-Command -session $session {$script}
    4  job2 BackgroundJob Completed True        localhost Invoke-Command -session $session {$script}
    6  job3 BackgroundJob Completed True        localhost Invoke-Command -session $session {$script}
    
    
  • #102373

    Rana Banerjee
    Participant

    @postanote Thanks a lot for your reply.

    I get errors while running the above example. however, I managed to get it working by adding "[System.Management.Automation.PSCredential][System.Management.Automation.Credential()]
    $Credential = [System.Management.Automation.PSCredential]::Empty,"
    Please see the code below.
    Many thanks

    Function Invoke-ParallelO365Jobs
    {
    	Param (
    		[Parameter()]
    		[ValidateNotNull()]
    		[System.Management.Automation.PSCredential][System.Management.Automation.Credential()]
    		$Credential = [System.Management.Automation.PSCredential]::Empty,
    		$types = @('UserMailbox', 'SharedMailbox')
    	)
    	
    	$InitialscriptBlock = {
    		Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
    		Import-Module ActiveDirectory
    	}
    	$scriptBlock = {
    		param (
    			$credential,
    			$Type
    		)
    		$paramNewPSSession = @{
    			ConfigurationName = 'Microsoft.Exchange'
    			ConnectionUri = 'https://outlook.office365.com/powershell-liveid/'
    			Credential = $Credential
    			Authentication = 'Basic'
    			AllowRedirection = $true
    		}
    		
    		$Session = New-PSSession @paramNewPSSession
    		$null = Import-PSSession $Session -AllowClobber -DisableNameChecking -Prefix EXO
    		Get-Exomailbox -RecipientTypeDetails $Type -WarningAction SilentlyContinue -ResultSize Unlimited | Select-Object PrimarySmtpAddress, RecipientTypeDetails
    	}
    	
    	foreach ($Type in $Types)
    	{
    		Start-Job -Name "$Type" -InitializationScript $InitialscriptBlock -ScriptBlock $scriptBlock -ArgumentList $credential, $Type
    	}
    	
    	While (@(Get-Job | Where-Object { $_.State -eq "Running" }).Count -ne 0)
    	{
    		$data = Receive-job -Name "*"
    		$result = $result + $data
    		Start-Sleep -Seconds 1
    		Write-host "Jobs recieved : $($Result.count)" -ForegroundColor Cyan
    	}
    	$result = $result + $data
    	get-job | Remove-Job
    	Write-Output $result
    }
    

You must be logged in to reply to this topic.