Thursday, September 25, 2008

Copying files from Desktop to a CE device

http://blogs.msdn.com/harsh/archive/2008/05/08/copying-files-from-desktop-to-a-ce-device.aspx

So I have this problem, where I need to create a tool that creates a folder with a bunch of files on the PC and then copies all these files over to a CE device. The method of choice here was via ActiveSync over USB. One of the design goals was to be able to copy these files to a number of devices simultaneously. However, research revealed that this is not really possible since, activesync only supports one active connection at a time. So much for the great idea.

So now we are faced with this problem of copying files over to the device over ActiveSync. Since the app had a lot of GUI, I created that in C#. The .NET framework exposes excellent API for file manipulation based on paths. The problem with ActiveSync connections is when the device is cradled, there is no drive letter associated with the connection. So all device paths are then \My Documents\ or \Program Files\ etc.

This creates a problem as far as the File manipulation API in System.IO. Since these methods accept relative as well as fully qualified paths, any path like the above, would be treated as a relative path and not a fully qualified path, thereby causing a lot of grief.

The only way to get past this so far has been to use RAPI.

RAPI stands for Remote Application Programming Interface, and was created ages ago with the first wave of activesync and windows mobile products.

RAPI has some documentation on MSDN at: http://msdn.microsoft.com/en-us/library/aa458022.aspx

However since RAPI is a native API, almost all the documentation is geared towards native implementation.

After searching the intertubes for a suitable solution, I couldn’t find one that was recent, and that was guaranteed to work.

Below is the solution:




using System;
using System.Threading;
using System.Runtime.InteropServices;
using COMTYPES = System.Runtime.InteropServices.ComTypes;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Logger = Microsoft.MobileDevices.Tools.Utilities.Logger;


namespace Microsoft.MobileDevices.Tools.RAPILibrary
{
public class RAPILibrary
{
# region RAPI EXTERN
// -------------------------------------------------------
// RAPI.DLL Definitions
// -------------------------------------------------------

public const int MAX_PATH = 260;
public const uint MAXDWORD = 0xFFFFFFFF;
public const uint S_OK = 0;

public struct RAPIINIT
{
public int cbSize;
public IntPtr heRapiInit;
public int hrRapiInit;
};

// From WINBASE.H -- for CeCreateFile
public enum ACCESS : uint
{
READ = 0x80000000,
WRITE = 0x40000000,
}
// From WINBASE.H -- for CeCreateFile
public enum SHARE
{
READ = 0x00000001,
WRITE = 0x00000002,
}
// From WINBASE.H -- for CeCreateFile
public enum ACTION
{
CREATE_NEW = 1,
CREATE_ALWAYS = 2,
OPEN_EXISTING = 3,
OPEN_ALWAYS = 4,
TRUNCATE_EXISTING = 5
}

// From WINBASE.H -- for CeCreateFile
public enum FILEFLAGATT : uint
{
ATTRIBUTE_READONLY = 0x00000001,
ATTRIBUTE_HIDDEN = 0x00000002,
ATTRIBUTE_SYSTEM = 0x00000004,
ATTRIBUTE_DIRECTORY = 0x00000010,
ATTRIBUTE_ARCHIVE = 0x00000020,
ATTRIBUTE_INROM = 0x00000040,
ATTRIBUTE_ENCRYPTED = 0x00000040,
ATTRIBUTE_NORMAL = 0x00000080,
ATTRIBUTE_TEMPORARY = 0x00000100,
ATTRIBUTE_SPARSE_FILE = 0x00000200,
ATTRIBUTE_REPARSE_POINT = 0x00000400,
ATTRIBUTE_COMPRESSED = 0x00000800,
ATTRIBUTE_OFFLINE = 0x00001000,
ATTRIBUTE_ROMSTATICREF = 0x00001000,
ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000,
ATTRIBUTE_ROMMODULE = 0x00002000,
FLAG_WRITE_THROUGH = 0x80000000,
FLAG_OVERLAPPED = 0x40000000,
FLAG_NO_BUFFERING = 0x20000000,
FLAG_RANDOM_ACCESS = 0x10000000,
FLAG_SEQUENTIAL_SCAN = 0x08000000,
FLAG_DELETE_ON_CLOSE = 0x04000000,
FLAG_BACKUP_SEMANTICS = 0x02000000,
FLAG_POSIX_SEMANTICS = 0x01000000,
}


///
/// Closes handle passed in
///

/// handle to be closed
///
[DllImport("rapi.DLL", CharSet = CharSet.Unicode)]
public static extern int CeCloseHandle(IntPtr hObject);

///
/// Creates file on device
///

/// path of file
/// Read/Write
/// Share mode
///
/// File creation options
/// Flags and attributes
///
/// pointer to the created file
[DllImport("rapi.DLL", CharSet = CharSet.Unicode)]
public static extern IntPtr CeCreateFile(string lpFileName, ACCESS dwDesiredAccess, SHARE dwShareMode, int Res1, ACTION dwCreationDisposition, FILEFLAGATT dwFlagsAndAttributes, int Res2);

///
/// Reads the file
///

/// handle to the file
/// buffer to copy data to
/// file size to read
/// file size read
///
///
[DllImport("rapi.DLL", CharSet = CharSet.Unicode)]
public static extern int CeReadFile(IntPtr hFile, IntPtr lpBuffer, int nNumberOfBytesToRead, ref int lpNumberOfBytesRead, int Reserved);

///
/// Writes file to device
///

/// handle of file to write to
/// data to write
/// length of buffer
/// length of file on device
///
///
[DllImport("rapi.DLL", CharSet = CharSet.Unicode)]
public static extern int CeWriteFile(IntPtr hFile, IntPtr lpBuffer, int nNumberOfBytesToWrite, ref int lpNumberOfBytesWritten, int Reserved);

///
/// Copies file from desktop to device
///

/// Full path of file on the desktop including filename
/// Path of file name on the device. '\' denotes root
public static void CopyFiletoDevice(string deskFile, string devFilePath)
{
try
{
int numTries=0;
// Verify file exists on desktop
FileInfo fInfo = new FileInfo(deskFile);
if (fInfo.Exists)
{
IntPtr devFileHandle;
// create a file on the device
do // force valid handle in the loop
{
devFileHandle = CeCreateFile(devFilePath, ACCESS.READ | ACCESS.WRITE, SHARE.READ | SHARE.WRITE, 0, ACTION.CREATE_ALWAYS, FILEFLAGATT.ATTRIBUTE_NORMAL, 0);
if (devFileHandle.ToInt32() < 0)
{
CeCloseHandle(devFileHandle);
}
} while (devFileHandle.ToInt32() < 0);


// read file from desktop into a byte buffer
Byte[] buffer;
ReadFileAsBinary(deskFile, out buffer);

int numBytesWritten = 0;
unsafe
{
fixed(byte* bufferPtr = buffer)
{
int retVal = CeWriteFile(devFileHandle, (IntPtr)(bufferPtr), (int)fInfo.Length, ref numBytesWritten, 0);
if (retVal == 0)
{
throw new IOException("RAPILibrary:CopyFileToDevice() Error Writing file on device. " + CeGetLastError().ToString());
}
}
}

// close handles
CeCloseHandle(devFileHandle);
}
else
{
Logger.WriteLog("RAPILibrary:CopyFileToDevice() File does not exist on desktop: " + deskFile);
}

}
catch (IOException ioEx)
{
Logger.WriteLog(ioEx.Message);
}
catch (Exception ex)
{
Logger.WriteLog(ex.ToString());
}
}

///
/// Reads file and returns byte[]
///

/// Full path of file
/// byte array for file data
private static void ReadFileAsBinary(string path, out Byte[] buffer)
{
Logger.WriteLog("RAPILibrary:ReadFileAsBinary() Reading the file " + path);
FileInfo fInfo = new FileInfo(path);
long fileLength = fInfo.Length;
buffer = new Byte[fileLength];
FileStream fs = fInfo.OpenRead();
fs.Read(buffer, 0, (int)fileLength);
fs.Close();
Logger.WriteLog("RAPILibrary:ReadFileAsBinary() Exit");
}
}
}

No comments: