Packagging script into file problem

Ask your Windows PowerShell-related questions, including questions on cmdlet development!
Forum rules
Do not post any licensing information in this forum.

Any code longer than three lines should be added as code using the 'Select Code' dropdown menu or attached as a file.
Locked
Bosparan
Posts: 282
Joined: Sun Mar 03, 2013 12:45 pm

Packagging script into file problem

Post by Bosparan » Mon Jul 29, 2013 5:55 am

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

jvierra
Posts: 14018
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: Packagging script into file problem

Post by jvierra » Mon Jul 29, 2013 9:00 am

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

jvierra
Posts: 14018
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: Packagging script into file problem

Post by jvierra » Mon Jul 29, 2013 9:02 am

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.

jvierra
Posts: 14018
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: Packagging script into file problem

Post by jvierra » Mon Jul 29, 2013 9:08 am

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
}

jvierra
Posts: 14018
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: Packagging script into file problem

Post by jvierra » Mon Jul 29, 2013 9:13 am

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.

Bosparan
Posts: 282
Joined: Sun Mar 03, 2013 12:45 pm

Re: Packagging script into file problem

Post by Bosparan » Tue Jul 30, 2013 4:56 am

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.)

jvierra
Posts: 14018
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: Packagging script into file problem

Post by jvierra » Tue Jul 30, 2013 5:23 am

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.

Locked