############################################################################### # # deployAndTestCe200x.eagle -- Windows CE Deployment & Testing Tool # # Written by Joe Mistachkin. # Released to the public domain, use at your own risk! # ############################################################################### package require Eagle proc usage { error } { if {[string length $error] > 0} then {puts stdout $error} puts stdout "usage:\ [file tail [info nameofexecutable]]\ [file tail [info script]] \[year\] \[platform\] \[configuration\]\ \[culture\] \[platformId\] \[deviceId\] \[quiet\]" # # NOTE: Indicate to the caller, if any, that we have failed. # exit 1 } # # NOTE: This procedure will output a diagnostic message, typically to the # standard output channel, using the [puts] command unless the global # variable "quiet" is non-zero. # proc qputs { args } { if {![info exists ::quiet] || !$::quiet} then { eval puts $args; flush stdout } } proc showPlatforms { cultureInfo } { set datastoreManager [object create -alias \ Microsoft.SmartDevice.Connectivity.DatastoreManager \ [$cultureInfo LCID]] object foreach -alias platform [$datastoreManager GetPlatforms] { qputs stdout [appendArgs \ "found platform \"" [$platform Id.ToString] "\" with name \"" \ [$platform Name] \"...] } } # # NOTE: This procedure looks up and returns the target device based on the # locale, platform Id, and device Id. # proc getDevice { cultureInfo platformId deviceId } { set datastoreManager [object create -alias \ Microsoft.SmartDevice.Connectivity.DatastoreManager \ [$cultureInfo LCID]] set platform [$datastoreManager -alias GetPlatform [object create \ Microsoft.SmartDevice.Connectivity.ObjectId $platformId]] if {[string length $deviceId] == 0} then { set deviceId [$platform GetDefaultDeviceId] } set device [$platform -alias GetDevice $deviceId] qputs stdout [appendArgs \ "returning device \"" [$device Name] "\" of platform \"" \ [$device Platform.ToString] "\" with Id \"" [$device Id.ToString] \ \"...] return $device } # # NOTE: This procedure starts a process on the target device and optionally # waits for it to complete. # proc startRemoteProcess { device fileName arguments {wait true} } { set remoteProcess [$device -alias GetRemoteProcess] if {![$remoteProcess Start $fileName $arguments]} then { error [appendArgs "could not start remote process \"" $fileName \"] } if {$wait} then { qputs stdout [appendArgs \ "waiting for remote process " [$remoteProcess GetId] ...] while {![$remoteProcess HasExited]} { qputs -nonewline stdout . after 1000 } qputs stdout "" return [$remoteProcess GetExitCode] } return -1 } set argc [llength $argv] if {$argc >= 0 && $argc <= 7} then { # # NOTE: Setup the default values for all command line options. # array set default { year 2008 platform {Pocket PC 2003 (ARMV4)} configuration Release culture en-US platformId 3c41c503-53ef-4c2a-8dd4-a8217cad115e deviceId {} quiet false } # # NOTE: Process all the command line options. If a command line option # is not present, use the default value. # set names [list \ year platform configuration culture platformId deviceId quiet] for {set index 0} {$index < [llength $names]} {incr index} { set name [lindex $names $index]; set value "" if {$argc > $index} then { set value [string trim [lindex $argv $index]] } if {[string length $value] > 0} then { set $name $value; set defaultValue false } else { set $name $default($name); set defaultValue true } qputs stdout [appendArgs \ "named parameter \"" $name "\" value is now \"" [set $name] \" \ [expr {$defaultValue ? " (default)" : ""}] .] } # # NOTE: Grab the culture instance based on the configured culture name. # set cultureInfo [object invoke -alias System.Globalization.CultureInfo \ GetCultureInfo $culture] # # NOTE: Build the list of .NET Compact Framework 2.0 packages that need to # be deployed to the target device, if necessary. # if {![info exists packages(2005)]} then { # # NOTE: The three letter Windows language name is needed when building # the default list of .NET Compact Framework packages because one # of them is a localized resource package. # set language3 [string toupper \ [$cultureInfo ThreeLetterWindowsLanguageName]] # # NOTE: The default list of .NET Compact Framework 2.0 packages contains # the .NET Compact Framework 2.0 installation CAB file for ARMV4 # on the Pocket PC and its associated resource installation CAB # files. # set packages(2005) [list \ abd785f0-cda7-41c5-8375-2451a7cbff26 \ \\Windows\\NETCFv2.ppc.armv4.cab \ c0ccf48e-4bfb-4d84-827c-981a595e40b4 \ [appendArgs \\Windows\\System_SR_ $language3 .cab]] } # # NOTE: Build the list of .NET Compact Framework 3.5 packages that need to # be deployed to the target device, if necessary. # if {![info exists packages(2008)]} then { # # NOTE: The two letter ISO language name is needed when building the # default list of .NET Compact Framework packages because one of # them is a localized resource package. # set language2 [string toupper \ [$cultureInfo TwoLetterISOLanguageName]] # # NOTE: The default list of .NET Compact Framework 3.5 packages contains # the .NET Compact Framework 3.5 installation CAB file for ARMV4 # on the Pocket PC and its associated resource installation CAB # files. # set packages(2008) [list \ abd785f0-cda7-41c5-8375-2451a7cbff37 \ \\Windows\\NETCFv35.ppc.armv4.cab \ c0ccf48e-4bfb-4d84-827c-981a595e40c5 \ [appendArgs \\Windows\\NETCFv35.Messages. $language2 .cab]] } # # NOTE: Save the path where this script is running from. # set path [file dirname [info script]] # # NOTE: The base path should be the project root directory, which should # be one level above the one containing this script. # set base_path [file dirname $path] # # NOTE: The managed binaries to be deployed to the target device should # be located in the "\bin\\Compact\bin" # directory. # set managed_directory [file join \ $base_path bin $year [appendArgs $configuration Compact] bin] # # NOTE: The native binaries to be deployed to the target device should # be located in the "\bin\\\" # directory. # set native_directory [file join \ $base_path bin $year $platform $configuration] # # NOTE: Build the list of all application files that need to be deployed to # the target device, including all the native and managed binaries. # if {![info exists fileNames]} then { # # NOTE: Grab the assembly name instance based on the primary managed # assembly file name. This is needed because the build portion of # the assembly version is used when building the default list of # application files to be deployed to the target device. # set assemblyName [object invoke -alias System.Reflection.AssemblyName \ GetAssemblyName [file join $managed_directory System.Data.SQLite.dll]] # # NOTE: The default list of application files includes the test application # itself, the System.Data.SQLite managed assembly, the SQLite interop # assembly, and the test application configuration file. # set fileNames [list [file join $managed_directory testce.exe] [file \ join $managed_directory System.Data.SQLite.dll] [file join \ $native_directory [appendArgs SQLite.Interop. [format %03d \ [$assemblyName Version.Build]] .dll]] [file join $managed_directory \ test.cfg] [file join $managed_directory test.sql]] } # # NOTE: Setup the directory on the target device where the application files # should be deployed to. # if {![info exists device_directory]} then { set device_directory "\\Program Files\\testce\\" } # # NOTE: Load the managed assembly that allows us to communicate with the # target device. If this fails, the necessary SDK components are # probably not available on this system. # # NOTE: As of Visual Studio 2013 Update 2 (?), this must specify the full # (versioned) assembly name here in order to make sure the assembly # associated with the Pocket PC 2003 platform(s) gets loaded. # object load [appendArgs \ "Microsoft.Smartdevice.Connectivity, Version=9.0.0.0, " \ "Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"] # # NOTE: Show the full list of available platforms. # showPlatforms $cultureInfo # # NOTE: Lookup the necessary device based on the platform and device Ids. # set device [getDevice $cultureInfo $platformId $deviceId] # # NOTE: Attempt to connect to the target device, which may be an emulator. # By default, we attempt to connect to the "Pocket PC 2003 SE Emulator" # device of the "Pocket PC 2003" platform (English). If this fails, # the target device is probably unavailable, either because it is not # connected or some SDK components are missing. # $device Connect # # NOTE: Grab the file deployer instance for the target device. This will # be used to download packages and send files to the target device. # set fileDeployer [$device -alias GetFileDeployer] # # NOTE: If the list of packages related to the configured build year do not # exist, skip this step. # if {[info exists packages($year)]} then { # # NOTE: Process each entry in the list of packages to be downloaded to the # target device. The package list must contain the package Id and # the file name (relative to the target device), in that order, for # each package to be downloaded to the target device. # foreach {packageId packageFileName} $packages($year) { qputs stdout [appendArgs \ "downloading package \"" $packageId "\" to device..."] $fileDeployer DownloadPackage [object create \ Microsoft.SmartDevice.Connectivity.ObjectId $packageId] qputs stdout [appendArgs \ "installing package file \"" $packageFileName "\" on device..."] startRemoteProcess $device wceload.exe [appendArgs "/noui " \ $packageFileName] } } # # NOTE: Process each application file to be sent to the target device. # foreach fileName $fileNames { qputs stdout [appendArgs \ "sending file \"" $fileName "\" to device..."] # # NOTE: All the application files are sent to the same directory on the # target device and the SendFile method requires a fully qualified # file name; therefore, grab the file name only from the source file # name and append that to the directory name on the target device. # Using [file join] and/or [file normalize] should be avoided here # because the directory name on the target device is not necessarily # valid a file name on this system and vice versa. # $fileDeployer SendFile $fileName [appendArgs $device_directory \ [file tail $fileName]] true false } # # NOTE: Run the test application on the target device in "automatic" mode # (i.e. no user interaction is required) and capture the exit code. # The exit code will be zero upon success (i.e. all tests passed) or # non-zero otherwise. # set testFileName [file nativename [file join $device_directory testce.exe]] # # NOTE: The first (and only) argument passed to "testce.exe" on the device # is the auto-close flag. When non-zero, it will close automatically # upon completion. Setting this to zero is sometimes useful in order # to more carefully examine the detailed results. # set exitCode [startRemoteProcess $device $testFileName true] # # NOTE: If an output file is generated by the remote process, where should # it be saved locally? # set outputFileName [file join $managed_directory output.txt] # # NOTE: Attempt to obtain the output file produced by the remote process, # if any. # catch { $fileDeployer ReceiveFile [appendArgs \ $device_directory [file tail $outputFileName]] $outputFileName false } # # NOTE: Is the target device actually an emulator running on this system? # set isEmulator [$device IsEmulator] # # NOTE: We no longer need to be connected to the target device. # $device Disconnect # # NOTE: Also, if the device is an emulator, attempt to shutdown the process # containing it now (since we probably caused it to start). # if {$isEmulator} then { # # NOTE: Try to find the top-level window for the device emulator process # based on the "LCDDisplay" window class name. Using this method # of finding the target window is somewhat fragile and may not work # reliably in the future. # set hWnd [lindex [lindex [info windows LCDDisplay] 0] 0]; # FIXME: ??? # # NOTE: Make sure we found it before trying to lookup the parent process. # if {[string is integer -strict $hWnd] && $hWnd != 0} then { # # NOTE: Attempt to lookup the parent process for the target window. # qputs stdout [appendArgs "found device emulator window handle " $hWnd \ ", looking up the process Id..."] set processId 0; set threadId 0; set error null if {[object invoke -flags +NonPublic \ Eagle._Components.Private.WindowOps GetWindowThreadProcessId \ [object create IntPtr $hWnd] processId threadId error] eq "Ok" && \ [string is integer -strict $processId] && $processId != 0} then { # # NOTE: This is not ideal; however, if we simply try to close the # target window, it will prompt to save state changes and that # requires user interaction. We never want to save the state; # therefore, just forcibly kill the process containing the # emulator. # qputs stdout [appendArgs "found device emulator process Id " \ $processId ", killing..."] kill -force $processId } } } # # NOTE: Print the overall result of running the test application and exit # using the exit code from the test application on the target device. # qputs stdout [expr {$exitCode == 0 ? "SUCCESS" : "FAILURE"}] exit $exitCode } else { usage "" }