Copying files from Desktop to a CE device
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: https://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,
}
/// <summary>
/// Closes handle passed in
/// </summary>
/// <param name="hObject">handle to be closed</param>
/// <returns></returns>
[DllImport("rapi.DLL", CharSet = CharSet.Unicode)]
public static extern int CeCloseHandle(IntPtr hObject);
/// <summary>
/// Creates file on device
/// </summary>
/// <param name="lpFileName">path of file</param>
/// <param name="dwDesiredAccess">Read/Write</param>
/// <param name="dwShareMode">Share mode</param>
/// <param name="Res1"></param>
/// <param name="dwCreationDisposition">File creation options</param>
/// <param name="dwFlagsAndAttributes">Flags and attributes</param>
/// <param name="Res2"></param>
/// <returns>pointer to the created file</returns>
[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);
/// <summary>
/// Reads the file
/// </summary>
/// <param name="hFile">handle to the file</param>
/// <param name="lpBuffer">buffer to copy data to</param>
/// <param name="nNumberOfBytesToRead">file size to read</param>
/// <param name="lpNumberOfBytesRead">file size read</param>
/// <param name="Reserved"></param>
/// <returns></returns>
[DllImport("rapi.DLL", CharSet = CharSet.Unicode)]
public static extern int CeReadFile(IntPtr hFile, IntPtr lpBuffer, int nNumberOfBytesToRead, ref int lpNumberOfBytesRead, int Reserved);
/// <summary>
/// Writes file to device
/// </summary>
/// <param name="hFile">handle of file to write to</param>
/// <param name="lpBuffer">data to write</param>
/// <param name="nNumberOfBytesToWrite">length of buffer</param>
/// <param name="lpNumberOfBytesWritten">length of file on device</param>
/// <param name="Reserved"></param>
/// <returns></returns>
[DllImport("rapi.DLL", CharSet = CharSet.Unicode)]
public static extern int CeWriteFile(IntPtr hFile, IntPtr lpBuffer, int nNumberOfBytesToWrite, ref int lpNumberOfBytesWritten, int Reserved);
/// <summary>
/// Copies file from desktop to device
/// </summary>
/// <param name="deskFile">Full path of file on the desktop including filename</param>
/// <param name="devFilePath">Path of file name on the device. '\' denotes root </param>
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());
}
}
/// <summary>
/// Reads file and returns byte[]
/// </summary>
/// <param name="path">Full path of file</param>
/// <param name="buffer">byte array for file data</param>
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");
}
}
}