private void processLinkInfoStream(string linkStream) { try { VirtualStreamReader reader = new VirtualStreamReader(_docStorage.GetStream(linkStream)); //there are two versions of the Link string, one contains ANSI characters, the other contains //unicode characters. //Both strings seem not to be standardized: //The length prefix is a character count EXCLUDING the terminating zero //Read the ANSI version Int16 cch = reader.ReadInt16(); byte[] str = reader.ReadBytes(cch); this.Link = Encoding.ASCII.GetString(str); //skip the terminating zero of the ANSI string //even if the characters are ANSI chars, the terminating zero has 2 bytes reader.ReadBytes(2); //skip the next 4 bytes (flags?) reader.ReadBytes(4); //Read the Unicode version cch = reader.ReadInt16(); str = reader.ReadBytes(cch * 2); this.Link = Encoding.Unicode.GetString(str); //skip the terminating zero of the Unicode string reader.ReadBytes(2); } catch (StreamNotFoundException) { } }
public WorkbookStream(string filePath) { using (var fs = new FileStream(filePath, FileMode.Open)) { StructuredStorageReader ssr = new StructuredStorageReader(fs); try { var wbStream = ssr.GetStream("Workbook"); byte[] wbBytes = new byte[wbStream.Length]; wbStream.Read(wbBytes, 0, wbBytes.Length, 0); _biffRecords = RecordHelper.ParseBiffStreamBytes(wbBytes); } catch (StreamNotFoundException) { var wbStream = ssr.GetStream("Book"); Console.WriteLine("WARNING: Main stream is in a Book record indicating legacy Excel 5 BIFF format. This may not parse correctly."); byte[] wbBytes = new byte[wbStream.Length]; wbStream.Read(wbBytes, 0, wbBytes.Length, 0); try { _biffRecords = RecordHelper.ParseBiffStreamBytes(wbBytes); } catch (Exception) { throw new NotImplementedException("Error parsing Book stream: Macrome currently doesn't support the Excel 5 BIFF format."); } } } }
public WorkbookStream(string filePath) { using (var fs = new FileStream(filePath, FileMode.Open)) { StructuredStorageReader ssr = new StructuredStorageReader(fs); var wbStream = ssr.GetStream("Workbook"); byte[] wbBytes = new byte[wbStream.Length]; wbStream.Read(wbBytes, 0, wbBytes.Length, 0); _biffRecords = RecordHelper.ParseBiffStreamBytes(wbBytes); } }
/// <summary> /// Ctor /// </summary> /// <param name="file"></param> public XlsDocument(StructuredStorageReader reader) { this.WorkBookData = new WorkBookData(); this.Storage = reader; if (reader.FullNameOfAllStreamEntries.Contains("\\" + WORKBOOK)) { this.workBookStreamReader = new VirtualStreamReader(reader.GetStream(WORKBOOK)); } else if (reader.FullNameOfAllStreamEntries.Contains("\\" + ALTERNATE1)) { this.workBookStreamReader = new VirtualStreamReader(reader.GetStream(ALTERNATE1)); } else { throw new ExtractorException(ExtractorException.WORKBOOKSTREAMNOTFOUND); } this.workBookExtr = new WorkbookExtractor(this.workBookStreamReader, this.WorkBookData); }
private static WorkbookStream LoadDecoyDocument(string decoyDocPath) { using (var fs = new FileStream(decoyDocPath, FileMode.Open)) { StructuredStorageReader ssr = new StructuredStorageReader(fs); var wbStream = ssr.GetStream("Workbook"); byte[] wbBytes = new byte[wbStream.Length]; wbStream.Read(wbBytes, 0, wbBytes.Length, 0); WorkbookStream wbs = new WorkbookStream(wbBytes); return(wbs); } }
public void DoTheMagic() { try { using (StructuredStorageReader reader = new StructuredStorageReader(this.Options.InputDocument)) { IStreamReader workbookReader = new VirtualStreamReader(reader.GetStream("Workbook")); using (StreamWriter sw = this.Options.Mode == BiffViewerMode.File ? File.CreateText(this.Options.OutputFileName) : new StreamWriter(Console.OpenStandardOutput())) { sw.AutoFlush = true; if (this.Options.PrintTextOnly) { PrintText(sw, workbookReader); } else { PrintHtml(sw, workbookReader); } if (!_isCancelled && this.Options.ShowInBrowser && this.Options.Mode == BiffViewerMode.File) { Util.VisitLink(this.Options.OutputFileName); } } } } catch (MagicNumberException ex) { if (this.Options.ShowErrors) { MessageBox.Show(string.Format("This file is not a valid Excel file ({0})", ex.Message), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { Console.WriteLine(ex.ToString()); } } catch (Exception ex) { if (this.Options.ShowErrors) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { Console.WriteLine(ex.ToString()); } } }
public static WorkbookStream GetMassSelectUDFArgumentSheet() { string template = AssemblyDirectory + Path.DirectorySeparatorChar + @"TestDocs\bug_report_1_mass_select_argument.xls"; using (var fs = new FileStream(template, FileMode.Open)) { StructuredStorageReader ssr = new StructuredStorageReader(fs); var wbStream = ssr.GetStream("Workbook"); byte[] wbBytes = new byte[wbStream.Length]; wbStream.Read(wbBytes, 0, wbBytes.Length, 0); return(new WorkbookStream(wbBytes)); } }
public static byte[] GetAutoOpenTestBytes() { string template = AssemblyDirectory + Path.DirectorySeparatorChar + @"TestDocs\auto_open_test.xls"; using (var fs = new FileStream(template, FileMode.Open)) { StructuredStorageReader ssr = new StructuredStorageReader(fs); var wbStream = ssr.GetStream("Workbook"); byte[] wbBytes = new byte[wbStream.Length]; wbStream.Read(wbBytes, 0, wbBytes.Length, 0); return(wbBytes); } }
public static WorkbookStream GetMacroLoopWorkbookStream() { string template = AssemblyDirectory + Path.DirectorySeparatorChar + @"TestDocs\macro-loop.xls"; using (var fs = new FileStream(template, FileMode.Open)) { StructuredStorageReader ssr = new StructuredStorageReader(fs); var wbStream = ssr.GetStream("Workbook"); byte[] wbBytes = new byte[wbStream.Length]; wbStream.Read(wbBytes, 0, wbBytes.Length, 0); return(new WorkbookStream(wbBytes)); } }
private static List<BiffRecord> GetDefaultMacroSheetRecords() { string defaultMacroPath = AssemblyDirectory + Path.DirectorySeparatorChar + @"default_macro_template.xls"; using (var fs = new FileStream(defaultMacroPath, FileMode.Open)) { StructuredStorageReader ssr = new StructuredStorageReader(fs); var wbStream = ssr.GetStream("Workbook"); byte[] wbBytes = new byte[wbStream.Length]; wbStream.Read(wbBytes, 0, wbBytes.Length, 0); WorkbookStream wbs = new WorkbookStream(wbBytes); //The last BOF/EOF set is our Macro sheet. List<BiffRecord> sheetRecords = wbs.GetRecordsForBOFRecord(wbs.GetAllRecordsByType<BOF>().Last()); return sheetRecords; } }
void Parse(StructuredStorageReader reader, int fibFC) { this.Storage = reader; this.WordDocumentStream = reader.GetStream("WordDocument"); //parse FIB this.WordDocumentStream.Seek(fibFC, System.IO.SeekOrigin.Begin); this.FIB = new FileInformationBlock(new VirtualStreamReader(this.WordDocumentStream)); //check the file version if ((int)this.FIB.nFib != 0) { if (this.FIB.nFib < FileInformationBlock.FibVersion.Fib1997Beta) { throw new ByteParseException("Could not parse the file because it was created by an unsupported application (Word 95 or older)."); } } else { if (this.FIB.nFibNew < FileInformationBlock.FibVersion.Fib1997Beta) { throw new ByteParseException("Could not parse the file because it was created by an unsupported application (Word 95 or older)."); } } //get the streams this.TableStream = reader.GetStream(this.FIB.fWhichTblStm ? "1Table" : "0Table"); try { this.DataStream = reader.GetStream("Data"); } catch (StreamNotFoundException) { this.DataStream = null; } //Read all needed STTBs this.RevisionAuthorTable = new StringTable(typeof(string), this.TableStream, this.FIB.fcSttbfRMark, this.FIB.lcbSttbfRMark); this.FontTable = new StringTable(typeof(FontFamilyName), this.TableStream, this.FIB.fcSttbfFfn, this.FIB.lcbSttbfFfn); this.BookmarkNames = new StringTable(typeof(string), this.TableStream, this.FIB.fcSttbfBkmk, this.FIB.lcbSttbfBkmk); this.AutoTextNames = new StringTable(typeof(string), this.TableStream, this.FIB.fcSttbfGlsy, this.FIB.lcbSttbfGlsy); //this.ProtectionUsers = new StringTable(typeof(String), this.TableStream, this.FIB.fcSttbProtUser, this.FIB.lcbSttbProtUser); // this.UserVariables = new StwStructure(this.TableStream, this.FIB.fcStwUser, this.FIB.lcbStwUser); //Read all needed PLCFs this.AnnotationsReferencePlex = new Plex <AnnotationReferenceDescriptor>(30, this.TableStream, this.FIB.fcPlcfandRef, this.FIB.lcbPlcfandRef); this.TextboxBreakPlex = new Plex <BreakDescriptor>(6, this.TableStream, this.FIB.fcPlcfTxbxBkd, this.FIB.lcbPlcfTxbxBkd); this.TextboxBreakPlexHeader = new Plex <BreakDescriptor>(6, this.TableStream, this.FIB.fcPlcfTxbxHdrBkd, this.FIB.lcbPlcfTxbxHdrBkd); this.OfficeDrawingPlex = new Plex <FileShapeAddress>(26, this.TableStream, this.FIB.fcPlcSpaMom, this.FIB.lcbPlcSpaMom); this.OfficeDrawingPlexHeader = new Plex <FileShapeAddress>(26, this.TableStream, this.FIB.fcPlcSpaHdr, this.FIB.lcbPlcSpaHdr); this.SectionPlex = new Plex <SectionDescriptor>(12, this.TableStream, this.FIB.fcPlcfSed, this.FIB.lcbPlcfSed); this.BookmarkStartPlex = new Plex <BookmarkFirst>(4, this.TableStream, this.FIB.fcPlcfBkf, this.FIB.lcbPlcfBkf); this.EndnoteReferencePlex = new Plex <short>(2, this.TableStream, this.FIB.fcPlcfendRef, this.FIB.lcbPlcfendRef); this.FootnoteReferencePlex = new Plex <short>(2, this.TableStream, this.FIB.fcPlcffndRef, this.FIB.lcbPlcffndRef); // PLCFs without types this.BookmarkEndPlex = new Plex <Exception>(0, this.TableStream, this.FIB.fcPlcfBkl, this.FIB.lcbPlcfBkl); this.AutoTextPlex = new Plex <Exception>(0, this.TableStream, this.FIB.fcPlcfGlsy, this.FIB.lcbPlcfGlsy); //read the FKPs this.AllPapxFkps = FormattedDiskPagePAPX.GetAllPAPXFKPs(this.FIB, this.WordDocumentStream, this.TableStream, this.DataStream); this.AllChpxFkps = FormattedDiskPageCHPX.GetAllCHPXFKPs(this.FIB, this.WordDocumentStream, this.TableStream); //read custom tables this.DocumentProperties = new DocumentProperties(this.FIB, this.TableStream); this.Styles = new StyleSheet(this.FIB, this.TableStream, this.DataStream); this.ListTable = new ListTable(this.FIB, this.TableStream); this.ListFormatOverrideTable = new ListFormatOverrideTable(this.FIB, this.TableStream); this.OfficeArtContent = new OfficeArtContent(this.FIB, this.TableStream); this.HeaderAndFooterTable = new HeaderAndFooterTable(this); this.AnnotationReferenceExtraTable = new AnnotationReferenceExtraTable(this.FIB, this.TableStream); this.CommandTable = new CommandTable(this.FIB, this.TableStream); this.AnnotationOwners = new AnnotationOwnerList(this.FIB, this.TableStream); //parse the piece table and construct a list that contains all chars this.PieceTable = new PieceTable(this.FIB, this.TableStream); this.Text = this.PieceTable.GetAllChars(this.WordDocumentStream); //build a dictionaries of all PAPX this.AllPapx = new Dictionary <int, ParagraphPropertyExceptions>(); for (int i = 0; i < this.AllPapxFkps.Count; i++) { for (int j = 0; j < this.AllPapxFkps[i].grppapx.Length; j++) { this.AllPapx.Add(this.AllPapxFkps[i].rgfc[j], this.AllPapxFkps[i].grppapx[j]); } } //build a dictionary of all SEPX this.AllSepx = new Dictionary <int, SectionPropertyExceptions>(); for (int i = 0; i < this.SectionPlex.Elements.Count; i++) { //Read the SED var sed = (SectionDescriptor)this.SectionPlex.Elements[i]; int cp = this.SectionPlex.CharacterPositions[i + 1]; //Get the SEPX var wordReader = new VirtualStreamReader(this.WordDocumentStream); this.WordDocumentStream.Seek(sed.fcSepx, System.IO.SeekOrigin.Begin); short cbSepx = wordReader.ReadInt16(); var sepx = new SectionPropertyExceptions(wordReader.ReadBytes(cbSepx - 2)); this.AllSepx.Add(cp, sepx); } //read the Glossary if (this.FIB.pnNext > 0) { this.Glossary = new WordDocument(this.Storage, (int)(this.FIB.pnNext * 512)); } }
static void Main(string[] args) { const int bytesToReadAtOnce = 512; char[] invalidChars = Path.GetInvalidFileNameChars(); TraceLogger.LogLevel = TraceLogger.LoggingLevel.Error; ConsoleTraceListener consoleTracer = new ConsoleTraceListener(); Trace.Listeners.Add(consoleTracer); Trace.AutoFlush = true; if (args.Length < 1) { Console.WriteLine("No parameter found. Please specify one or more compound document file(s)."); return; } foreach (string file in args) { StructuredStorageReader storageReader = null; DateTime begin = DateTime.Now; TimeSpan extractionTime = new TimeSpan(); try { // init StorageReader storageReader = new StructuredStorageReader(file); // read stream _entries ICollection <DirectoryEntry> streamEntries = storageReader.AllStreamEntries; //ICollection<DirectoryEntry> allEntries = storageReader.AllEntries; //allEntries.Add(storageReader.RootDirectoryEntry); List <DirectoryEntry> allEntries = new List <DirectoryEntry>(); allEntries.AddRange(storageReader.AllEntries); allEntries.Sort( delegate(DirectoryEntry a, DirectoryEntry b) { return(a.Sid.CompareTo(b.Sid)); } ); //foreach (DirectoryEntry entry in allEntries) //{ // Console.WriteLine(entry.Sid + ":"); // Console.WriteLine("{0}: {1}", entry.Name, entry.LengthOfName); // Console.WriteLine("CLSID: " + entry.ClsId); // string hexName = ""; // for (int i = 0; i < entry.Name.Length; i++) // { // hexName += String.Format("{0:X2} ", (UInt32)entry.Name[i]); // } // Console.WriteLine("{0}", hexName); // UInt32 left = entry.LeftSiblingSid; // UInt32 right = entry.RightSiblingSid; // UInt32 child = entry.ChildSiblingSid; // Console.WriteLine("{0:X02}: Left: {2:X02}, Right: {3:X02}, Child: {4:X02}, Name: {1}, Color: {5}", entry.Sid, entry.Name, (left > 0xFF) ? 0xFF : left, (right > 0xFF) ? 0xFF : right, (child > 0xFF) ? 0xFF : child, entry.Color.ToString()); // Console.WriteLine("----------"); // Console.WriteLine(""); //} // create valid path names Dictionary <DirectoryEntry, KeyValuePair <string, Guid> > pathNames1 = new Dictionary <DirectoryEntry, KeyValuePair <string, Guid> >(); foreach (DirectoryEntry entry in allEntries) { string name = entry.Path; for (int i = 0; i < invalidChars.Length; i++) { name = name.Replace(invalidChars[i], '_'); } pathNames1.Add(entry, new KeyValuePair <string, Guid>(name, entry.ClsId)); } // Create Directory Structure StructuredStorageWriter sso = new StructuredStorageWriter(); sso.RootDirectoryEntry.setClsId(storageReader.RootDirectoryEntry.ClsId); foreach (DirectoryEntry entry in pathNames1.Keys) { StorageDirectoryEntry sde = sso.RootDirectoryEntry; string[] storages = entry.Path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < storages.Length; i++) { if (entry.Type == DirectoryEntryType.STGTY_ROOT) { continue; } if (entry.Type == DirectoryEntryType.STGTY_STORAGE || i < storages.Length - 1) { StorageDirectoryEntry result = sde.AddStorageDirectoryEntry(storages[i]); sde = (result == null) ? sde : result; if (i == storages.Length - 1) { sde.setClsId(entry.ClsId); } continue; } VirtualStream vstream = storageReader.GetStream(entry.Path); sde.AddStreamDirectoryEntry(storages[i], vstream); } } // Write sso to stream MemoryStream myStream = new MemoryStream(); sso.write(myStream); // Close input storage storageReader.Close(); storageReader = null; // Write stream to file byte[] array = new byte[bytesToReadAtOnce]; int bytesRead; string outputFileName = Path.GetFileNameWithoutExtension(file) + "_output" + Path.GetExtension(file); string path = Path.GetDirectoryName(Path.GetFullPath(file)); outputFileName = path + "\\" + outputFileName; FileStream outputFile = new FileStream(outputFileName, FileMode.Create, FileAccess.Write); myStream.Seek(0, SeekOrigin.Begin); do { bytesRead = myStream.Read(array, 0, bytesToReadAtOnce); outputFile.Write(array, 0, bytesRead); } while (bytesRead == array.Length); outputFile.Close(); // --------- extract streams from written file storageReader = new StructuredStorageReader(outputFileName); // read stream _entries streamEntries = storageReader.AllStreamEntries; // create valid path names Dictionary <string, string> pathNames2 = new Dictionary <string, string>(); foreach (DirectoryEntry entry in streamEntries) { string name = entry.Path; for (int i = 0; i < invalidChars.Length; i++) { name = name.Replace(invalidChars[i], '_'); } pathNames2.Add(entry.Path, name); } // create output directory string outputDir = '_' + (Path.GetFileName(outputFileName)).Replace('.', '_'); outputDir = outputDir.Replace(':', '_'); outputDir = Path.GetDirectoryName(outputFileName) + "\\" + outputDir; Directory.CreateDirectory(outputDir); // for each stream foreach (string key in pathNames2.Keys) { // get virtual stream by path name IStreamReader streamReader = new VirtualStreamReader(storageReader.GetStream(key)); // read bytes from stream, write them back to disk FileStream fs = new FileStream(outputDir + "\\" + pathNames2[key] + ".stream", FileMode.Create); BinaryWriter writer = new BinaryWriter(fs); array = new byte[bytesToReadAtOnce]; do { bytesRead = streamReader.Read(array); writer.Write(array, 0, bytesRead); writer.Flush(); } while (bytesRead == array.Length); writer.Close(); fs.Close(); } // close storage storageReader.Close(); storageReader = null; extractionTime = DateTime.Now - begin; Console.WriteLine("Streams extracted in " + String.Format("{0:N2}", extractionTime.TotalSeconds) + "s. (File: " + file + ")"); } catch (Exception e) { Console.WriteLine("*** Error: " + e.Message + " (File: " + file + ")"); Console.WriteLine("*** StackTrace: " + e.StackTrace + " (File: " + file + ")"); } finally { if (storageReader != null) { storageReader.Close(); } } } }
public OleObject(CharacterPropertyExceptions chpx, StructuredStorageReader docStorage) { this._docStorage = docStorage; this.ObjectId = getOleEntryName(chpx); this.Path = "\\ObjectPool\\" + this.ObjectId + "\\"; processOleStream(this.Path + "\u0001Ole"); if (this.fLinked) { processLinkInfoStream(this.Path + "\u0003LinkInfo"); } else { processCompObjStream(this.Path + "\u0001CompObj"); } //get the storage entries of this object this.Streams = new Dictionary <string, VirtualStream>(); foreach (string streamname in docStorage.FullNameOfAllStreamEntries) { if (streamname.StartsWith(this.Path)) { this.Streams.Add(streamname.Substring(streamname.LastIndexOf("\\") + 1), docStorage.GetStream(streamname)); } } //find the class if of this object foreach (DirectoryEntry entry in docStorage.AllEntries) { if (entry.Name == this.ObjectId) { this.ClassId = entry.ClsId; break; } } }
public PowerpointDocument(StructuredStorageReader file) { try { this.CurrentUserStream = file.GetStream("Current User"); var rec = Record.ReadRecord(this.CurrentUserStream); if (rec is CurrentUserAtom) { this.CurrentUserAtom = (CurrentUserAtom)rec; } else { this.CurrentUserStream.Position = 0; var bytes = new byte[this.CurrentUserStream.Length]; this.CurrentUserStream.Read(bytes); string s = Encoding.UTF8.GetString(bytes).Replace("\0", ""); } } catch (InvalidRecordException e) { throw new InvalidStreamException("Current user stream is not valid", e); } // Optional 'Pictures' stream if (file.FullNameOfAllStreamEntries.Contains("\\Pictures")) { try { this.PicturesStream = file.GetStream("Pictures"); this.PicturesContainer = new Pictures(new BinaryReader(this.PicturesStream), (uint)this.PicturesStream.Length, 0, 0, 0); } catch (InvalidRecordException e) { throw new InvalidStreamException("Pictures stream is not valid", e); } } this.PowerpointDocumentStream = file.GetStream("PowerPoint Document"); try { this.DocumentSummaryInformationStream = file.GetStream("DocumentSummaryInformation"); ScanDocumentSummaryInformation(); } catch (StructuredStorage.Common.StreamNotFoundException) { //ignore } if (this.CurrentUserAtom != null) { this.PowerpointDocumentStream.Seek(this.CurrentUserAtom.OffsetToCurrentEdit, SeekOrigin.Begin); this.LastUserEdit = (UserEditAtom)Record.ReadRecord(this.PowerpointDocumentStream); } this.ConstructPersistObjectDirectory(); this.IdentifyDocumentPersistObject(); this.IdentifyMasterPersistObjects(); this.IdentifySlidePersistObjects(); this.IdentifyOlePersistObjects(); this.IdentifyVbaProjectObject(); }
static void Main(string[] args) { const int bytesToReadAtOnce = 1024; char[] invalidChars = Path.GetInvalidFileNameChars(); TraceLogger.LogLevel = TraceLogger.LoggingLevel.Error; ConsoleTraceListener consoleTracer = new ConsoleTraceListener(); Trace.Listeners.Add(consoleTracer); Trace.AutoFlush = true; if (args.Length < 1) { Console.WriteLine("No parameter found. Please specify one or more compound document file(s)."); return; } foreach (string file in args) { StructuredStorageReader storageReader = null; DateTime begin = DateTime.Now; TimeSpan extractionTime = new TimeSpan(); try { // init StorageReader storageReader = new StructuredStorageReader(file); // read stream _entries ICollection <DirectoryEntry> streamEntries = storageReader.AllStreamEntries; // create valid path names Dictionary <string, string> PathNames = new Dictionary <string, string>(); foreach (DirectoryEntry entry in streamEntries) { string name = entry.Path; for (int i = 0; i < invalidChars.Length; i++) { name = name.Replace(invalidChars[i], '_'); } PathNames.Add(entry.Path, name); } // create output directory string outputDir = '_' + file.Replace('.', '_'); outputDir = outputDir.Replace(':', '_'); Directory.CreateDirectory(outputDir); // for each stream foreach (string key in PathNames.Keys) { // get virtual stream by path name IStreamReader streamReader = new VirtualStreamReader(storageReader.GetStream(key)); // read bytes from stream, write them back to disk FileStream fs = new FileStream(outputDir + "\\" + PathNames[key] + ".stream", FileMode.Create); BinaryWriter writer = new BinaryWriter(fs); byte[] array = new byte[bytesToReadAtOnce]; int bytesRead; do { bytesRead = streamReader.Read(array); writer.Write(array, 0, bytesRead); writer.Flush(); } while (bytesRead == array.Length); writer.Close(); fs.Close(); } // close storage storageReader.Close(); storageReader = null; extractionTime = DateTime.Now - begin; Console.WriteLine("Streams extracted in " + String.Format("{0:N2}", extractionTime.TotalSeconds) + "s. (File: " + file + ")"); } catch (Exception e) { Console.WriteLine("*** Error: " + e.Message + " (File: " + file + ")"); } finally { if (storageReader != null) { storageReader.Close(); } } } }