Need some help...

  • Thread starter Thread starter Bruce W. Darby
  • Start date Start date
B

Bruce W. Darby

This will be my very first VB.Net application and it's pretty simple. But
I've got a snag in my syntax somewhere. Was hoping that someone could point
me in the right direction.

The history:
My work involves creating custom packages of our software product for golf
courses that purchase our software. The course data is kept as a back up in
the event the course needs us to replace their custom files. Each course has
a folder of it's own data under a centralized directory.

The problem:
The custom files are going to become a serious storage issue as our customer
base increases.

The solution:
Compress each course folder into an individual .cab file containing all of
the course's custom files and then archive these files to storage media such
as CD/DVD.

Where I need help:
I found some code for implementing a command line window and passing it a
string through a stream. The command window is being instantiated, but the
string is not getting passed or my syntax for running the makecab utility is
off-base. I've spent several hours looking for sample code that would
explain the makecab syntax and I've also tried to determine if the
parameters are being passed in to the Sub correctly. I've set a break at the
line where the string should be passed to the command window, but I can not
seem to get a respnse from the watches that have been set. I've included a
copy of the sub that I'm using to compress the files. If this is not the
correct forum for this, I do apologize. A nudge in the correct direction
would be deeply appreciated.

Private Sub CompressFolder(ByVal ToFolder As String, ByVal FromFolder As
String, ByVal FinalFile As String)

Dim CompressProcess As Process = New Process

CompressProcess.StartInfo.FileName = "cmd.exe"

CompressProcess.StartInfo.UseShellExecute = False

CompressProcess.StartInfo.CreateNoWindow = True

CompressProcess.StartInfo.RedirectStandardInput = True

CompressProcess.StartInfo.RedirectStandardOutput = True

CompressProcess.StartInfo.RedirectStandardError = True

CompressProcess.Start()

Dim stmStreamIn As IO.StreamWriter = CompressProcess.StandardInput

stmStreamIn.AutoFlush = True

Dim stmStreamOut As IO.StreamReader = CompressProcess.StandardOutput

Dim stmStreamErr As IO.StreamReader = CompressProcess.StandardError

stmStreamIn.Write("makecab.exe /L %ToFolder% %FromFolder% %FinalFile%" &
System.Environment.NewLine)

stmStreamIn.Write("exit" & System.Environment.NewLine)

If Not CompressProcess.HasExited Then

CompressProcess.Kill()

End If

stmStreamIn.Close()

stmStreamOut.Close()

stmStreamErr.Close()

End Sub
 
Nice idea, but you're slightly on the wrong track with your usage of
makecab.exe.

The /L switch can only be used to compress a single source file into a
single .cab file, so unless you want gazillions of .cab files that's not
much good.

The /F switch is used to compress multiple source files into a into a single
..cab file, but it's a bit more complicated that you think.

Makecab.exe does not use standardinput/output. With the /F switch it reads
the required information from a definition file and it, in addition to
creating the .cab file, writes summary information to a 'report' file and
other information to a 'inf' file. Don't even consider the whys and
wherefores of this because what you are attempting to do is slight
non-standard compared to what makecab.exe was actually designed for.

Now for the nitty gritty.

The first thing you need to do is write a definition file, lets call it
test.ddf for the purpose of the exercise:

Dim _sw As New StreamWriter("test.ddf")
_sw.WriteLine(".Set SourceDir=" & FromFolder)
_sw.WriteLine(".Set CabinetNameTemplate=" & FinalFile)
_sw.WriteLine(".Set DiskDirectoryTemplate=" & ToFolder)
_sw.WriteLine(".Set InfFileName=test.inf")
_sw.WriteLine(".Set RptFileName=test.rpt")
_sw.WriteLine(".Set Cabinet=ON")
_sw.WriteLine(".Set Compress=ON")
Dim _files as String() = Directory.GetFiles(FromFolder)
For Each _file as String In _files
_sw.WriteLine(File.GetFileName(_file))
Next
_sw.Close()

Makecab.exe is very unforgiving if you get the syntax of the definition file
wrong.

If FromFolder does not exist then it will fail.

It will create the folder indicated by the final node of ToFolder, but it
will fail if any of the folders higher up the hierachy do not exist. This
can be circumvented by executing:

If Not Directory.Exists(Path.GetDirectory(ToFolder)) Then
Directory.CreateDirectory(Path.GetDirectory(ToFolder))

Note that ALL the files that you want in the .cab file MUST be included in
the definition file. As you can see this is easily achieved by iterating
through all the files in FromFolder. Also note that baecause .Set SourceDir
is specified, only the actual filename is required and not the full path.

The next thing is to execute makecab.exe:

Process.Start("makecab.exe", "/F test.ddf").WaitForExit()

A command window will momentarily appear and disappear.

Now you will probably want to see what happened. Plonk a TextBox on the
form, make it multiline, set it's Font to your favourite mono-spaced font
and size it to a decent size.

TextBox1.Text = File.ReadAllText("test.rpt")

Now all you have to do is tidy up after yourself:

File.Delete("test.ddf")
File.Delete("test.inf")
File.Delete("test.rpt")

Supress the File.Delete's if you want to have a look in the test.inf file
but I doubt whether the content of it will be of any value to you given what
you are trying to achieve.

The following link will alow you to download the a file called Cabsdk.exe
hich contains, among other things, a Word document that provides a lot of
detail about makecab.exe.

http://download.microsoft.com/download/platformsdk/cab/2.0/w98nt42kmexp/en-us/Cabsdk.exe

Have fun!
 
Stephany,

Thank you so very much. I will give this a go when I get home from work. The
link to the CabSDK I've already downloaded, but I didn't see the .doc, so
I'll go through my download again. Cheers!
 
This will be my very first VB.Net application and it's pretty simple. But
I've got a snag in my syntax somewhere. Was hoping that someone could point
me in the right direction.

The history:
My work involves creating custom packages of our software product for golf
courses that purchase our software. The course data is kept as a back up in
the event the course needs us to replace their custom files. Each course has
a folder of it's own data under a centralized directory.

The problem:
The custom files are going to become a serious storage issue as our customer
base increases.

The solution:
Compress each course folder into an individual .cab file containing all of
the course's custom files and then archive these files to storage media such
as CD/DVD.

If file size is paramount you might want to consider using better
compression formats (rar, bzip, etc). You can use sharpziplib to accomplish
this sort of thing.

http://www.icsharpcode.net/OpenSource/SharpZipLib/Default.aspx
 
Rad,

I have downloaded the SharpZip files and am reading into them, but at this
point in time, I doubt my ability as a programmer to be able to incorporate
this on a module level. I do understand that there are better compression
methods available, but I'd like to start small and work my way up the
ladder, so to speak. Thank you very much for your assistance.

Bruce
 
It sounds like losser mentality to me, insecurities in your ability to
learn. Maybe programming just isn't the right thing for you. Don't feel
too ashamed, not everyone can reach the highest levels. You can always
try something else less demanding.

The Grand Master
 
Rad said:
If file size is paramount you might want to consider using better
compression formats (rar, bzip, etc). You can use sharpziplib to
accomplish
this sort of thing.

http://www.icsharpcode.net/OpenSource/SharpZipLib/Default.aspx

Took a look into the sharpziplib you suggested and found it easier than
usual to add it to my solution, but I am having a bit of an issue. Not sure
what I'm doing wrong and hope that you can enlightem my fuzzy brain. When I
start the compression, I get an UnauthorizedAccessException thrown as the
program trys to recurse through the subfolders in FromFolder. I can
understand that it's treating the subdirectories as files, but cannot figure
out why. Any tweaks or nudges? Code follows...

Private Sub CompressFolder()

Dim strSourceDir As String = txtSelectDir.Text

If strSourceDir.Length = 0 Then

MsgBox("Please specify a directory")

txtSelectDir.Focus()

Exit Sub

Else

If Not Directory.Exists(strSourceDir) Then

MsgBox("Directory not found")

txtSelectDir.Focus()

Exit Sub

End If

End If

Dim strTargetDir As String = txtWriteDir.Text

If strTargetDir.Length = 0 Then

MsgBox("Please specify a directory")

txtWriteDir.Focus()

Exit Sub

Else

If Not Directory.Exists(strTargetDir) Then

MsgBox("Directory not found")

txtWriteDir.Focus()

Exit Sub

End If

End If

Dim arystrmFolderNames() As String = Directory.GetDirectories(strSourceDir)

Dim strmZipOutputStream As ZipOutputStream

Dim intCounter As Integer

Dim strTargetFile As String = strTargetDir &
arystrmFolderNames(intCounter).LastIndexOfAny("\")

strmZipOutputStream = New ZipOutputStream(File.Create(strTargetFile))

strmZipOutputStream.SetLevel(9)

Dim strFolder As String

For Each strFolder In arystrmFolderNames

Dim strmFile As FileStream = File.OpenRead(strFolder)

Dim abyBuffer(strmFile.Length - 1) As Byte

strmFile.Read(abyBuffer, 0, abyBuffer.Length)

Dim objZipEntry As ZipEntry = New ZipEntry(strFolder)

objZipEntry.DateTime = DateTime.Now

objZipEntry.Size = strmFile.Length

strmFile.Close()

strmZipOutputStream.PutNextEntry(objZipEntry)

strmZipOutputStream.Write(abyBuffer, 0, abyBuffer.Length)

intCounter += 1

Next

strmZipOutputStream.Finish()

strmZipOutputStream.Close()

MsgBox("Operation completed")

End Sub

P.S. Please forgive my sloppy coding
 
Stephany,

I've gotten a whole lot more understanding of makecab because of your
assistance. I attempted to modify the code to recurse subfolders, but
couldn't get the modified code to work. Started digging around on the
Internet and in an obscure corner found this site.
http://www.codeproject.com/cs/files/CABCompressExtract.asp

Have you ever heard of or used this tool?

Bruce
 
You sure are having a bit of an issue!!!!!! :)

Before you even attempt to go any further, I stringly recommend that you
'turn' Option Strict and Option Explicit 'on' for ALL your VB.NET projects
and solutions. You can do this in Tools/Options. Expand the Projects and
Solutions node and select VB Defaults. Settings those to 'on' in there will
ensure that Option Strict and Option Explicit are automatically 'on' for all
new projects. For existing projects you will need to change the settings for
each individual project.

Option Explicit 'on' means that you must explicitly define each variable
before you can use it.

Option Strict 'on' means that the complier will make NO assumptions about
what the code should do. When you turn it on for this project than a number
of compile-time 'errors' will appear and you will need to correct them all.

A number of the problems in the code fragment are caused by Option Strict
being 'off'. If you leave it turned off then you will continue to tear your
hair out because you not be able to spot various subtle problems.

Now for the nitty gritty.

Dim arystrmFolderNames() As String =
Directory.GetDirectories(strSourceDir)

This line create a string array that contains 1 element for each sub-folder
in whatever folder strSourceDir is pointing to. Each element contains the
name of (or the path to) a single sub-folder.

With the line (inside the loop):

Dim strmFile As FileStream = File.OpenRead(strFolder)

you are attempting to open a sub-folder for reading and that is where you
are falling over. A folder (or directory) cannot be opened as if it were a
file. You need to walk the 'tree' and deal with each and every file in each
and every sub-folder.

Before we deal with that, the line:

Dim strTargetFile As String = strTargetDir &
arystrmFolderNames(intCounter).LastIndexOfAny("\")

must also be causing you problems. If your strTargetDir is "C:\Temp\" and
arystrmFolderNames(intCounter) is "C:\Data\SubA" then strTargetFile will
become "C:\Temp\7" and I don't really think that is what you intend. I
suspect that you are looking for strTargetFile to be "C:\Temp\SubA.zip". If
that is what you are looking for than you need to use something like:

Dim strTargetFile As String =
Path.ChangeExtension(Path.Combine(strTargetDir,
Path.GetFileName(arystrmFolderNames(intCounter))), "zip")

You use the phrase 'as the program trys to recurse through the subfolders in
FromFolder', but what your code is, in fact, doing is iterating through the
subfolders in FromFolder.

You do actually need to recursively walk the tree that starts at FromFolder,
dealing with every sub-folder including nested sub-folders and handling
every file that you encounter. This can be done with something like:

Private Sub RecurseFolders(ByVal folder as String)

Dim _files as String() = Directory.GetFiles(folder)

For Each _file As String In _files
Dim strmFile As FileStream = File.OpenRead(_file)
Dim abyBuffer(strmFile.Length - 1) As Byte
strmFile.Read(abyBuffer, 0, abyBuffer.Length)
strmFile.Close()
Dim objZipEntry As New ZipEntry(_file)
objZipEntry.DateTime = DateTime.Now
objZipEntry.Size = abyBuffer.Length
strmZipOutputStream.PutNextEntry(objZipEntry)
strmZipOutputStream.Write(abyBuffer, 0, abyBuffer.Length)
Next

Dim _folders As String() = Directory.GetDirectories(folder)

For Each _folder in _folders
RecurseFolders(FromFolder)
Next

End Sub

Call this method for the appropriate place with:

RecurseFolders(FromFolder)

Let us know how you get on.
 
I haven't come across that one before but it is certainly one of the better
presented articles and projects at CodeProject.

I had a dig in the source code and it looks OK. The CompressFolder will
compress everything in a given tree and this means that you wouldn't have to
worry abot doing any recursion yourself. I note that it also has the ability
to encrypt the compressed data.

I can only suggest that you try it and see if it will work for you. If you
do then also test the extraction methods to ensure that you can actually
'restore' the data from the compressed file.

Out of interest, what sort of data quantity are you talking about in a given
tree. Are you talking about multi-Gigabytes, because if you are then you
could hit some 'ceiling' or other.

Let us know how you get on.
 
Nothing massive. Each course folder may contain 1-2 subfolder's and a
maximum size of 20 MB. Nothing near the 2 GB limit. If I can't get the other
stuff with SharpZipLib to work, I may follow up on that one. Thanks for the
reply.

Bruce
 
Stephany,

Checked VB Defaults and Option Explicit was already on. Set Option Strict to
on and got two error's from my code, which I've resolved, with a little help
from a 'friend'. hehehehe I'll give this a shot and let you know what
happens. Thanks a boatload! from an old, senile wannabe! :)

Bruce
 
Stephany,

THIS IS IT! :) I downloaded the .NET project from CodeProject last night and
messed around a bit with building the .dll before figuring out that it was
already built. LOL I then added it to the solution and started working with
the coding. Took me about 30 minutes to work it out with some of the code
that you so graciously provided in the SharZiplib examples. Fired up the
project and started stepping through the compression routine in my program
and it actually WORKED! Gee, guess I gotta think of myself as a bit more
than senile now, though I'll be danged if I can remember why... The cab file
is created in MS-Zip format, so it opened right up by double-clicking on the
..zip file. And it properly recursed all subdirectories. I'm almost finished
and am hoping that this will be the beginning of a fruitful learning
experience for me.

Again, thanks ever so much for the assistance.

Bruce
 
Stephany,

I owe you a debt of gratitude. With your assistance, I was able to find a
way to do what I wanted to do, even though I don't consider myself a
developer by any mean's. I now have something I can take to work and use to
make things a bit easier on my techs. While I doubt that the coding is all
that elegant, here is the final sub that I was able to come up with that is
doing the job I need it to do. Again, thanks for all the assistance. Happy
Holidays.

Private Sub CompressFolder()

Dim strSourceDir As String = txtSelectDir.Text

Dim strTargetDir As String = txtWriteDir.Text

Dim _Folders As String() = Directory.GetDirectories(strSourceDir)

prbProgress.Minimum = 0

prbProgress.Maximum = CInt(_Folders.Length)

prbProgress.Step = 1

For Each _Folder As String In _Folders

Dim strFldr2Compress As String = Path.GetFileName(_Folder)

Dim strFinalFile As String = Path.ChangeExtension(Path.Combine(strTargetDir,
_

Path.GetFileName(strFldr2Compress)), "cab")

Dim i_Compress As CabLib.Compress = New CabLib.Compress()

i_Compress.CompressFolder(strTargetDir, strFinalFile, "", 0)

prbProgress.PerformStep()

lblProgress.Text = ("Compressing: " & strFinalFile)

Next

End Sub
 
May I make one small suggestion since you might be in the mood to tweak it a
bit at this time?

Reducing dependencies is a good goal to keep in mind and you've set up a
dependency here that is very easy to eliminate. The SourceDir and TargetDir
in your example are supplied by two components which "must" be named what
you have them named. Any attempt to use the CompressFolder() routine in any
other way will fail.

Simply modify CompressFolder() so that it accepts strSourceDir and
strTargetDir as parameters passed to it. The call will then supply the
..Text properties of your two textboxes (in your example) but it can be used
in other situations by simply passing two properly composed strings. They
don't have to be textboxes and they don't have to be named what you have
them named.

Tom
 
Tom,

Always open to constructive opinions. :) If I'm understanding what you're
putting forth....

The procedure call would be something like...

CompressFolder(txtSelectDir.Text, txtWriteDir.Text)

And the resulting procedure would receive the information by the
following...

Private Sub CompressFolder(ByVal strSourceDir As String, ByVal strTargetDir
As String)

Then strSourceDir and strTargetDir would not need to be Dim'd as they
already contain the information required from the calling procedure?
 
If I understand what you are trying to achieve correctly then you have a
folder structure that resembles;

C:\Top
Sub1
SubSub11
SubSub12
SubSub13
Sub2
SubSub21
SubSub22
SubSub23
Sub3
SubSub31
SubSub32
SubSub33

and what you want to end up with is a struncture that resembles:

C:\Compress
Sub1.cab
Sub2.cab
Sub3.cab

If that is the case then you logic is a bit flawed. The line
'i_Compress.CompressFolder(strTargetDir, strFinalFile, "", 0)' should
probable read 'i_Compress.CompressFolder(_Folder, strFinalFile, "", 0)'.

Apart from that, 7 out of 10 and here are your notes: :)

Dim strSourceDir As String = txtSelectDir.Text
Dim strTargetDir As String = txtWriteDir.Text

These 2 lines are completely redundant. Their values are seldom referred to
and never modified therefore the values can be used directly from the
TextBox controls. Before proceeding, I would be inclined to trim the content
of both TextBoxes to ensure that any leading and/or trailing whitespace,
that may be present, is removed. You may also like to add some validation to
ensure that values have been entered and the values entered are, in fact,
valid.

prbProgress.Minimum = 0

Unless you mave modified the value of prbProgress.Minimum elsewhere in your
app, (which is probalbly unlikely) then there is no need to set it here
because it is already 0. However, it is more likely that you have used the
ProgressBar elsewhere in you app so it is important to make sure that it
starts it's counting from 0 by resetting the value of prbProgress.Value.

prbProgress.Maximum = CInt(_Folders.Length)

The Length property of an array returns an Integer so there is no need to
carry out any conversion when assigning it to any other object that is also
an Integer.

The Path.GetFileName(strFldr2Compress) in the 2nd line of the loop is, at
the least, redundant, but more importantly it is downright confusing. The
first line of the loop get the last node of the folder name and stores it in
a variable. To do carry out the operation again on the variable is
absolutely not necessary.

Now we can note that the only time that strFldr2Compress is used is in the
very next line and therefore the use of an interim variable is not
necessary.

Dim i_Compress As CabLib.Compress = New CabLib.Compress()

In VB.Net this can be shortened to 'Dim i_Compress As New CabLib.Compress'.
Also, the instantiation of i_Compress is being carried out on every
iteration of the loop, whereas it only needs to carried out once.

lblProgress.Text = ("Compressing: " & strFinalFile)

At the point that this is executed the compression operation has already
finished so this line should appear earlier in the loop.

At the 2 points where UI elements are 'updated', the app is in a faily tight
loop and you may find that the ProgressBar and/or Label do not 'update' as
you might expect them to. A couple of forced updates will be of assistance
here to allow the UI Elements to repaint in a timely manner.

The modified method is shown below:

Private Sub CompressFolder()

txtSelectDir.Text = txtSelectDir.Text.Trim()

txtWriteDir.Text = txtWriteDir.Text.Trim()

Dim _folders As String() = Directory.GetDirectories(txtSelectDir.Text)

prbProgress.Value = 0

prbProgress.Maximum = _folders.Length

prbProgress.Step = 1

Dim _compress As New CabLib.Compress

For Each _folder As String In _folders
Dim _finalfile As String =
Path.ChangeExtension(Path.Combine(txtWriteDir.Text,
Path.GetFileName(_folder)), "cab")
lblProgress.Text = ("Compressing: " & _finalfile)
lblProgress.Update()
_compress.CompressFolder(_folder, _finalfile, "", 0)
prbProgress.PerformStep()
prbProgress.Update()
Next

End Sub

As you can see, it is now a bit leaner without the redundant variables and
with the loop having no extraneous logic in it.

Although I invariably use hugarian notation for names of controls as you
have done, (even if you didn't know you were doing it), I like to use
lowercase with a preceding underscore for naming all local variables. I find
that this gives me continual visual prompts as to the scope of objects.

Of course, all this is my opinion, and you should code in a style that suits
you.

Have fun dissect that lot :)
 
Yes, That's exactly what Tom is saying and it's a good idea too.

If you're feeling bold, you could reduce the dependencies further by raising
events insteading of updating the UI elements directly. That would mean that
you could get even bolder and launch CompressFolder as a thread and let your
app get on with other stuff while the compression is happening.

We'll make a half-decent programmer out of you yet :)
 
I thought I'd take one step at a time Stephany :-)

Bruce... You've got it. And Stephany's first idea is another sensible and
common way to separate "process" from UI.

Rather than update a specific (and specifically named) UI element your
CompressFolder() procedure should raise an "I've processed one" event. You
may even opt to have it raise multiple events, "I've started", "I'm
processing" and "I'm done". Now watch... you (in one scenario) decide to
have a progress bar update, then you simply tell the progress bar to listen
for and react to "I'm processing" events. Every time it "hears" one it
moves the progress bar. In some other scenario you may decide to have the
words "Processing Folder <whatever>" appear in a label. So this time you
tell the label to listen to "I'm processing" events instead and write code
the UI code there. Nothing in the UI knows what CompressFolder does and
CompressFolder has no idea about forms, buttons and progress bars. Needless
to say you would name your events something more sensible than "I'm
processing" but that's what you're telling the event listeners.

Note that to get <whatever> (the folder name) to appear in an event handler
you will have to send that information along when you raise the event.
Again there is an entire event system available to us for doing exactly
this. You send any information that the UI needs to know about in the
"args" argument and an event handler will receive it.

Threading? When you have everything else working smoothly you might look
into it. There can be concurrency issues so you have to think it through.
The nice thing is (as Stephany ponts out) you can use the app while the
compressing is going on. You would have to make certain that nothing you do
interferes with the compressing operation while it is running. There are
ways this can be done and among them setting a "compressing" flag turns on
when the process starts and off when it is done. The rest of the app can
check the flag and disable various actions (when needed) with the most
obvious action to prevent being the "compress folders" option. :-)

Try the events thing it will amaze you.

Tom
 
Stephany,

After posting last night, I ran back through my code and there was a LOT of
stuff that I discovered about my code being incomplete and illogical.*sigh*
I was pointing one directory too high AND was not using the correct source
to draw from. Took a bit for me to work it out, but found that I was using
the destination folder as my source folder and was getting some really weird
results. Tom's post last night and yours this morning really helped me get
things ironed out in my mind. Incorporating the parts that you posted this
morning, here is my new compression sub, hopefully all neat and tidy. I know
that this one works as it should. LOL I've also decided to use labels with
autoellilpsis turned on in place of the text boxes. I did this as a personal
attribute because of space issues and my own anal perspective toward
neatness. I am doing the sanity check on the label entries from a seperate
sub so that is taken care of. If the user doesn't select a From and To
folder, it won't go anwhere but back to the folderbrowserdialog. Thanks
again for all the help.

The (hopefully) almost completed compression sub...

Private Sub CompressFolder(ByVal strSourceDir As String, ByVal strTargetDir
As String)

Dim _Folders As String() = Directory.GetDirectories(strSourceDir)

prbProgress.Maximum = _Folders.Length

prbProgress.Step = 1

prbProgress.Visible = True

lblProgress.Visible = True

For Each _Folder As String In _Folders

Dim strFinalFile As String = Path.ChangeExtension(Path.Combine(strTargetDir,
_

Path.GetFileName(_Folder)), "cab")

lblProgress.Text = ("Compressing: " & Path.GetFileName(_Folder))

lblProgress.Update()

Dim i_Compress As New CabLib.Compress

i_Compress.CompressFolder(_Folder, strFinalFile, "", 0)

prbProgress.PerformStep()

prbProgress.Update()

Next

prbProgress.Visible = False

lblProgress.Visible = False

End Sub
 
Back
Top