Page 1 of 1

Packagging script into file problem

Posted: Mon Jul 29, 2013 5:55 am
by Bosparan
Hello fellow scripters,

I am trying to create a calendar sync using EWS. This functionen is trying to mess with me though: It works just fine if run as script (and returns all the appointments in the calendar), but returns $null if run after packaging (PowerShell 2, standard manifest for elevation). To be more accurate, it returns $null if "try" succedes and an array of $nulls if I have to catch the error. The number of $nulls in that array is dependant on the number of iterations, not the number of calendar appointments.
PowerShell Code
Double-click the code block to select all.
function Get-EWSAppointments([Microsoft.Exchange.WebServices.Data.CalendarFolder]$calendarfolder,[System.DateTime]$from,[System.DateTime]$to,$service)
{
	# Interrupt if no time-span exists
	if ($from -eq $to)
	{
		Write-Error -Message "Timespan can't be 0 seconds"
	}
	
	else 
	{
		# Switch time if in wrong order
		if (($from.CompareTo($to)) -eq 1)
		{
			$Temp = $from
			$from = $to
			$to = $Temp
		}
		
		# Get Appointments and return them
		try
		{
			$Calendarview = new-object Microsoft.Exchange.WebServices.Data.CalendarView($from,$to)
			$Calendarview.PropertySet = [Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties
			$testcv = $Calendarview
			$Error.Clear()
			$results = $calendarfolder.FindAppointments($Calendarview)
			if (($service -ne $null) -and ($results.TotalCount -gt 0))
			{
				$schema = [Microsoft.Exchange.WebServices.Data.ItemSchema]::Body
				$property = New-Object Microsoft.Exchange.WebServices.Data.PropertySet($schema)
				$property.BasePropertySet = [Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties
				$results = $service.LoadPropertiesForItems($results,$property)
				$script:tempResults = $results
				$ResultSet = @()
				if ($tempResults.Count -gt 0)
				{
					$i = 0
					while ($i -lt $tempResults.Count)
					{
						$Temp = $tempResults.get_Item($i)
						if ($Temp.Result.value__ -eq 0){$ResultSet += $Temp.Item}
						$i++
					}
				}
				return $ResultSet
			}
			else 
			{
				return $results
			}
		}
		catch
		{
			$temp2 = $Error[0]
			if ($temp2.Exception.Message.Contains("Sie haben die maximale Anzahl von Objekten überschritten"))
			{
				# Calculate middle value between dates
				$middle = $to - $from
				$middle = $from.Add([System.TimeSpan]::FromSeconds(($middle.TotalSeconds / 2)))
				
				# Do the same call again twice, but this time with each only half the time
				$ResultSet = @()
				$ResultSet += Get-EWSAppointments -calendarfolder $calendarfolder -from $from -to $middle -service $service
				$ResultSet += Get-EWSAppointments -calendarfolder $calendarfolder -from $middle -to $to -service $service
				
				return $ResultSet
			}
		}		
	}
}
Any help appreciated.
Regards,
Bosparan

Re: Packagging script into file problem

Posted: Mon Jul 29, 2013 9:00 am
by jvierra
The following is what your code looks like when properly indented and structured for readability. I think you will se that the result you are getting is due to the erros in the design of the function.

I am not sure what you are expecting but start with this.
PowerShell Code
Double-click the code block to select all.
function Get-EWSAppointments{
    Param(
        [Microsoft.Exchange.WebServices.Data.CalendarFolder]$calendarfolder,
        [System.DateTime]$from,
        [System.DateTime]$to,
        $service
    )
    
    # Interrupt if no time-span exists
    if ($from -eq $to){
        Write-Error -Message "Timespan can't be 0 seconds"
        return
    }

    # Get Appointments and return them
    try{
        $Calendarview = new-object Microsoft.Exchange.WebServices.Data.CalendarView($from,$to)
        $Calendarview.PropertySet = [Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties
        $testcv = $Calendarview
        $results = $calendarfolder.FindAppointments($Calendarview)
        if (($service -ne $null) -and ($results.TotalCount -gt 0)){
            $schema = [Microsoft.Exchange.WebServices.Data.ItemSchema]::Body
            $property = New-Object Microsoft.Exchange.WebServices.Data.PropertySet($schema)
            $property.BasePropertySet = [Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties
            $results = $service.LoadPropertiesForItems($results,$property)
            $script:tempResults = $results
            $ResultSet = @()
            if ($tempResults.Count -gt 0){
                $i = 0
                while ($i -lt $tempResults.Count){
                    $Temp = $tempResults.get_Item($i)
                    if ($Temp.Result.value__ -eq 0){$ResultSet += $Temp.Item}
                    $i++
                }
            }
            return $ResultSet
        }else{
            return $results
        }
    }

    catch{
        if ($_.Exception.Message.Contains("Sie haben die maximale Anzahl von Objekten überschritten")){
            # Calculate middle value between dates
            $middle = $to - $from
            $middle = $from.Add([System.TimeSpan]::FromSeconds(($middle.TotalSeconds / 2)))
            
            # Do the same call again twice, but this time with each only half the time
            $ResultSet = @()
            $ResultSet += Get-EWSAppointments -calendarfolder $calendarfolder -from $from -to $middle -service $service
            $ResultSet += Get-EWSAppointments -calendarfolder $calendarfolder -from $middle -to $to -service $service
            
            return $ResultSet
        }
    }
}

Re: Packagging script into file problem

Posted: Mon Jul 29, 2013 9:02 am
by jvierra
In a 'Catch' block the $_ is the current error.

It is a very bad habit to clear errors unless you now exactly what you are doing. This will leave behind code that hides the error history making debugging very hard at times.

Re: Packagging script into file problem

Posted: Mon Jul 29, 2013 9:08 am
by jvierra
This version fixes some of the design issues but I do not have the setup to test it fully. You need to rethink this and try to avoid recursion until you understand how PowerShell handles these things. We generally don't want to process during a 'catch' as we are in the error handler. This can have some very bad consequences.
PowerShell Code
Double-click the code block to select all.
function Get-EWSAppointments{
    Param(
        [Microsoft.Exchange.WebServices.Data.CalendarFolder]$calendarfolder,
        [System.DateTime]$from,
        [System.DateTime]$to,
        $service
    )
    
    # Interrupt if no time-span exists
    if ($from -eq $to){
        Write-Error -Message "Timespan can't be 0 seconds"
        return
    }

    # Get Appointments and return them
    try{
        $Calendarview = new-object Microsoft.Exchange.WebServices.Data.CalendarView($from,$to)
        $Calendarview.PropertySet = [Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties
        $testcv = $Calendarview
        $results = $calendarfolder.FindAppointments($Calendarview)
        if (($service -ne $null) -and ($results.TotalCount -gt 0)){
            $schema = [Microsoft.Exchange.WebServices.Data.ItemSchema]::Body
            $property = New-Object Microsoft.Exchange.WebServices.Data.PropertySet($schema)
            $property.BasePropertySet = [Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties
            $results = $service.LoadPropertiesForItems($results,$property)
            $script:tempResults = $results
            if ($tempResults.Count -gt 0){
                $i = 0
                while ($i -lt $tempResults.Count){
                    $Temp = $tempResults.get_Item($i)
                    if ($Temp.Result.value__ -eq 0){$ResultSet += $Temp.Item}
                    $i++
                }
            }
        }
    }

    catch{
        if ($_.Exception.Message.Contains("Sie haben die maximale Anzahl von Objekten überschritten")){
        
            # Calculate middle value between dates
            $middle = $to - $from
            $middle = $from.Add([System.TimeSpan]::FromSeconds(($middle.TotalSeconds / 2)))
            
            # Do the same call again twice, but this time with each only half the time
            $ResultSet+=@(Get-EWSAppointments -calendarfolder $calendarfolder -from $from -to $middle -service $service)
            $ResultSet+=@(Get-EWSAppointments -calendarfolder $calendarfolder -from $middle -to $to -service $service)
            
        }
    }
    return $ResultSet
}

Re: Packagging script into file problem

Posted: Mon Jul 29, 2013 9:13 am
by jvierra
Looking at your code I cannot figure out what you are trying to do. Nothing in EWS is this hard. Just get the enrries an enumerate them.

Re: Packagging script into file problem

Posted: Tue Jul 30, 2013 4:56 am
by Bosparan
Hello jvierra,

thanks for the answers. I found the problem on the msdn Exchange forum and thus I thought I'd drop a note on what the problem was, why I didn't find it and how it all turned out.
Final version:
PowerShell Code
Double-click the code block to select all.
function Get-EWSAppointments
{
	Param(
		[Microsoft.Exchange.WebServices.Data.CalendarFolder]$calendarfolder,
		[System.DateTime]$from,
		[System.DateTime]$to,
		[Microsoft.Exchange.WebServices.Data.ExchangeService]$service
	)
	
	# Interrupt if no time-span exists
	if ($from -eq $to)
	{
		Write-Error -Message "Timespan can't be 0 seconds"
		Return $null
	}
	
	else 
	{
		# Switch time if in wrong order
		if (($from.CompareTo($to)) -eq 1)
		{
			$Temp = $from
			$from = $to
			$to = $Temp
		}
		
		# Setup variables
		$test = $true
		$appointments = @()
		
		# Keep doing it until you have it all
		while ($test)
		{
			# Get Items without any information beyond ID
			$Calendarview = new-object Microsoft.Exchange.WebServices.Data.CalendarView($from,$to,1000)
			$property = New-Object Microsoft.Exchange.WebServices.Data.PropertySet
			$Calendarview.PropertySet = $property
			$results = $folder.FindAppointments($Calendarview)
						
			# Convert Items to other format
			$type = ("System.Collections.Generic.List"+'`'+"1") -as "Type"
			$type = $type.MakeGenericType("Microsoft.Exchange.WebServices.Data.Item" -as "Type")
			$Items = [Activator]::CreateInstance($type)
			foreach($item in $results.Items)
			{
				[Microsoft.Exchange.WebServices.Data.Item]$item = $item
				$Items.Add($item)
			}
			
			# Load the relevant properties
			$property.Add([Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Body)
			$property.Add([Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Subject)
			$property.Add([Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Start)
			$property.Add([Microsoft.Exchange.WebServices.Data.AppointmentSchema]::End)
			$service.LoadPropertiesForItems($Items,$property) | Out-Null
			
			# Add the items to the storage array
			foreach ($item in $results.Items){$appointments += $item}
			
			# if more is available, load the next set of appointments
			if ($results.MoreAvailable)
			{
				$from = $appointments[($appointments.Count - 1)].Start
			}
			
			# If no more is available, end iteration
			else
			{
				$test = $false
			}
		}
		
		Return $appointments
	}
}
Now the fun thing about this function: It wouldn't work when run in PowerShell Studio 2012, but it does work just fine after compiling it into an .exe file.

The problem was indeed a data-type conversion problem, which is being adressed before and in the first foreach loop:
PowerShell Code
Double-click the code block to select all.
# Convert Items to other format
$type = ("System.Collections.Generic.List"+'`'+"1") -as "Type"
$type = $type.MakeGenericType("Microsoft.Exchange.WebServices.Data.Item" -as "Type")
$Items = [Activator]::CreateInstance($type)
foreach($item in $results.Items)
{
    [Microsoft.Exchange.WebServices.Data.Item]$item = $item
    $Items.Add($item)
}
Thanks for pointing out that the parameter could be handled a bit better than I originally had, I've been doing a rush job but that's no excuse for writing hard-to-read code. Didn't know about recursion during catches either, thanks, I'll watch it. As for the other comments / questions:
jvierra wrote:Nothing in EWS is this hard. Just get the enrries an enumerate them.
Well, it's the getting that has been a bit troublesome. Especially when I try to load like 6-7k appointments. Every odd minute or so.
jvierra wrote:The following is what your code looks like when properly indented and structured for readability.
That's very much a matter of taste and -paricularly - perception. Not saying you're wrong when applied to your own reading of code, but for me, my notation is the most comfortable (note that that does not refer to how I originally handled parameters: that had been sloppiness.)

Re: Packagging script into file problem

Posted: Tue Jul 30, 2013 5:23 am
by jvierra
Sorry about the indent issue. I looked at it on the RSS feed and forgot that this web site makes the RSS look like hell in IE10. When I locked at this page I see that your indenting was in tact and well done. I prefer a different style, but, as you pointed out, that is a matter of taste.

Wish someone would fix the RSS.

Glad you found an answer.
Yes - remember that, when in a catch, you are in the middle of a stack unwind which does not complete until yoo fall out of the catch. If another exception occurs things can get bad.