Author Posts

June 10, 2018 at 12:19 pm

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
}

June 10, 2018 at 11:42 pm

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.

June 11, 2018 at 2:42 am

@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++
}

June 11, 2018 at 11:15 pm

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}

June 13, 2018 at 4:06 am

@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
}