Thursday, June 21, 2012

Access VBA: Creating Text File or Table from Query


Access is both a handy tool and a pain in the neck to use. It removes all of the complexities of using SQL Server, DB2, or Oracle and provides a nice point and click interface. However, It does not follow all of the SQL rules and will not do full outer joins. Also, Access documentation and support sucks. It does provide a VBA interface that allows us to code around the problems.

Running a systems analysis on an IBM mainframe involves many people. We exchange information using Access. It's not a great database but is a very handy tool in that it is powerful enough to run SQL queries, a database and all of the queries can be copied in a single file with no special setup requirements, handles a lot of data import and export, and the management can use it too.

I was analyzing z/OS JCL and needed to run a stored query 90 times to get a sample set of data for 90 days. I came up with two solutions. The first created a text file using tab delimiters and the second built a table dynamically. The core code should work for any record set created from any query.

This code just sets the parameters and runs the queries: 
Function fRunGet90()
    Dim sd As Date
    Dim ed As Date
    Dim strFileName As String
    Dim cntr As Long
    strFileName = CurrentProject.Path & "\qryUse_90Days.txt"
    
    sd = #12/15/2011 7:00:00 AM#
    ed = #3/14/2012 7:00:00 AM#
    
    cntr = fGet90(sd, ed, strFileName, 90)    
    Debug.Print cntr
    
    cntr = fGet90_2()    
    Debug.Print cntr

End Function

 The function fGet90() takes a start and end date, an output text file name and  the number of days (iterations) to run as arguments. DAO.QueryDef is used to run the Access stored query. DAO stands for Data Access Objects, the native library Microsoft exposing objects in Access. Except for earlier versions of Access like 2000 and 2002, all versions have this library. 
Create the text file for output and set the name of the query in the QueryDef object. 
Function fGet90(sd As Date, ed As Date, strFileName As String, days As Integer) As Long
' ---------------------------------------------------------------
'
' This Access 2007 VBA program creates a text file from multiple calls to a stored

' Access query that takes parameters
'
' ---------------------------------------------------------------
' From start and end dates like these:
' 2011/12/15 07:00:00
' 2012/03/14 07:00:00
' run days times and gather the results incrementing 1 day at a time
' ---------------------------------------------------------------

Dim dbDatabase As DAO.Database
Dim rsRecordset As DAO.Recordset
Dim qdParameters As DAO.QueryDef
Dim str As String
Dim cntr As Long

cntr = 0

Open strFileName For Output As #1 ' create the output text file

Set dbDatabase = CurrentDb()

' set the query name

Set qdParameters = dbDatabase.QueryDefs("qryUse") 
The first loop controls the call to the query and at the top the start and end dates are set for the run and a record set is generated by calling the stored query: 
For DayCount = 1 To days
    
    ' update the date parameters
    qdParameters.Parameters("StartDate") = sd     '"2011/12/15 07:00:00"
    qdParameters.Parameters("EndDate") = ed       '"2012/03/14 09:00:00"

    Set rsRecordset = qdParameters.OpenRecordset()
    
    With rsRecordset
        rsRecordset.MoveFirst

        ' place the field names
 When processing the record set for the first time use a flag, or in this case a counter, to test if this is the first iteration and  loop through the recordset fields to create a column title list. Notice the separator can be any character and tabs a used because they cut and paste so easily into Excel (another ubiquitous off tool). If using the comma name the file with a .CSV suffix for comma separated values and it can be opened directly by Excel. I used tabs due to embedded commas in several field of the data: 
        If Not rsRecordset.EOF And cntr = 0 Then ' only write out the field names once
            str = vbNullString
            For Each fld In rsRecordset.Fields
                str = str & fld.Name & vbTab
                'str = str & fld.Name & ","
            Next
            str = Mid(str, 1, Len(str) - 1) '--remove last separator
            Print #1, str 'write the titles
        End If

        Do
            str = vbNullString
            For Each fld In rsRecordset.Fields
                'str = str & fld & ","
                str = str & fld & vbTab
            Next
            str = Mid(str, 1, Len(str) - 1) '--remove last separator
            Print #1, str ' write the line to the text file
            
            cntr = cntr + 1
    
            rsRecordset.MoveNext

        Loop While Not rsRecordset.EOF    End With

    sd = DateAdd("d", 1, sd) ' add a day to the start and end dates
    ed = DateAdd("d", 1, ed)
 Take care to close and reset the recordset at the end of each call to the query. If you do not you will quickly run out of and overwrite memory areas and Access has a nasty habit of using the same memory to store your code and run your code and will clobber all of it and possibly corrupt the database too: 
    rsRecordset.Close
    Set rsRecordset = Nothing
Next

 Close it all and end the function: 
Close #1 ' close the text file

dbDatabase.Close
Set dbDatabase = Nothing

fGet90 = cntr

End Function

 This version is exactly the same as the first except it takes no parameters and creates a database table rather than a text file. Access has no function to provide a proper filed type name to be used in data definition language (DDL) so the function  FieldTypeName() (thanks to Allen Browne) provides a translation of the field type codes: 
Function fGet90_2() As Long
' -----------------------------------------------------------------------------------
'
' This Access 2007 VBA program creates a table in the same databse from multiple calls ' to a stored Access query that takes parameters.
'
' -----------------------------------------------------------------------------------

Dim sd As Date
Dim ed As Date
Dim TableName As String
Dim days As Integer

TableName = "Use90Days"

sd = #12/15/2011 7:00:00 AM#
ed = #3/14/2012 7:00:00 AM#
days = 90

Dim dbDatabase As DAO.Database
Dim rsRecordset As DAO.Recordset
Dim qdParameters As DAO.QueryDef
Dim str As String
Dim cntr As Long
Dim rsTargetRecordset As DAO.Recordset

cntr = 0

Set dbDatabase = CurrentDb()

' set the query name

Set qdParameters = dbDatabase.QueryDefs("qryUse")

For DayCount = 1 To days ' run the query and process the record set n times placing all into a target table
    
    ' update the date parameters
    qdParameters.Parameters("StartDate") = sd     '"2011/12/15 07:00:00"
    qdParameters.Parameters("EndDate") = ed       '"2012/03/14 07:00:00"

    ' Get and set the record set
    Set rsRecordset = qdParameters.OpenRecordset()
    
    'Process the recordset
    With rsRecordset
        rsRecordset.MoveFirst ' or just MoveFirst since we are using With
        
        ' place the field names inot a create table string
        
 Processing the recordset and creating the table on the first iteration: 
        If Not rsRecordset.EOF And cntr = 0 Then ' only create the table once
            'str = vbNullString
            ' delete the table if it exists

 Destroy any existing version of the table but if it does not exist the code will stop so use a scan: 
            ' this fails if the table does not exist
            'str = "DROP TABLE " & TableName & " ;"
            'dbDatabase.Execute (str)

            ' So use this which will always work
            For i = 0 To dbDatabase.TableDefs.Count - 1
                If dbDatabase.TableDefs(i).Name = TableName Then
                    ' this fails if the table is open in Access
                    dbDatabase.TableDefs.Delete (TableName)
                    Exit For
                End If
            Next
            
 Create the new table and a recordset for it: 
            str = "create table " & TableName & " ("
            For Each fld In rsRecordset.Fields
                str = str & fld.Name & " " & FieldTypeName(fld) & ","
            Next
            str = Mid(str, 1, Len(str) - 1) '--remove last comma
            str = str & " );"
            
            ' Create the table
            dbDatabase.Execute (str)
            
            ' set the target table recordset
            Set rsTargetRecordset = dbDatabase.OpenRecordset(TableName)

        End If
        
 Notice that AddNew and Update must be called for the target recordset each time a new record is to be added: 
        ' Process each record
        Do
            rsTargetRecordset.AddNew ' create a new record
            
            ' process each field and place it in the table
      
            For Each fld In rsRecordset.Fields
                rsTargetRecordset.Fields(fld.Name) = rsRecordset.Fields(fld.Name)
            Next
            
            rsTargetRecordset.Update ' write the record
            
            cntr = cntr + 1
    
            rsRecordset.MoveNext
            
        Loop While Not rsRecordset.EOF

    End With

    sd = DateAdd("d", 1, sd) ' add a day to the start and end dates
    ed = DateAdd("d", 1, ed)

 Here code is added to ensure the recordset object exists and is capable of being closed: 
    If Not rsRecordset Is Nothing Then
        rsRecordset.Close
        Set rsRecordset = Nothing
    End If

Next

 Close and exit the function: 
If Not rsTargetRecordset Is Nothing Then
    rsTargetRecordset.Close
    Set rsTargetRecordset = Nothing
End If

dbDatabase.Close
Set dbDatabase = Nothing

fGet90_2 = cntr

End Function

 FieldTypeName() is just a big case statement returning a field type name as a string but it is a big help. The original code used a DAO.Field as an argument and returned a report formatted field type name. Using a variant type argument was required to make this code work in Access 2007. Note that not all of this code is tested and some of the values returned may not be DDL syntax correct due to problems finding anything in Microsoft's Access documentation: 
Function FieldTypeName(fld As Variant) As String
' -----------------------------------------------------
' Modified from original code by Allen Browne April 2010
' -----------------------------------------------------

    Dim strReturn As String    'Name to return

    Select Case CLng(fld.Type) 'fld.Type is Integer, constants are Long

        Case dbBoolean: strReturn = "YESNO"                   ' 1
        Case dbByte: strReturn = "Byte"                       ' 2
        Case dbInteger: strReturn = "Integer"                 ' 3
        Case dbLong                                           ' 4
            If (fld.Attributes And dbAutoIncrField) = 0& Then
                strReturn = "LONG"
            Else
                strReturn = "AutoNumber"
            End If
        Case dbCurrency: strReturn = "Currency"               ' 5
        Case dbSingle: strReturn = "Single"                   ' 6
        Case dbDouble: strReturn = "Double"                   ' 7
        Case dbDate: strReturn = "DateTime"                   ' 8
        Case dbBinary: strReturn = "Binary"                   ' 9 (no interface)
        Case dbText                                           '10
            If (fld.Attributes And dbFixedField) = 0& Then
                strReturn = "Text"
            Else
                strReturn = "Text (" & fld.Size & ")"         '(no interface)
            End If
        Case dbLongBinary: strReturn = "LONGBINARY"           '11 (an OLE Object)
        Case dbMemo                                           '12
            If (fld.Attributes And dbHyperlinkField) = 0& Then
                strReturn = "Memo"
            Else
                strReturn = "Hyperlink"
            End If
        Case dbGUID: strReturn = "GUID"                       '15

        'Attached tables only: cannot create these in JET

        Case dbBigInt: strReturn = "BigInt"                   '16
        Case dbVarBinary: strReturn = "VarBinary"             '17
        Case dbChar: strReturn = "Char"                       '18
        Case dbNumeric: strReturn = "Numeric"                 '19
        Case dbDecimal: strReturn = "Decimal"                 '20
        Case dbFloat: strReturn = "Float"                     '21
        Case dbTime: strReturn = "Time"                       '22
        Case dbTimeStamp: strReturn = "TimeStamp"             '23

        'Constants for complex types don't work prior to Access 2007

        Case 101&: strReturn = "Attachment"            'dbAttachment
        Case 102&: strReturn = "Complex Byte"          'dbComplexByte
        Case 103&: strReturn = "Complex Integer"       'dbComplexInteger
        Case 104&: strReturn = "Complex Long"          'dbComplexLong
        Case 105&: strReturn = "Complex Single"        'dbComplexSingle
        Case 106&: strReturn = "Complex Double"        'dbComplexDouble
        Case 107&: strReturn = "Complex GUID"          'dbComplexGUID
        Case 108&: strReturn = "Complex Decimal"       'dbComplexDecimal
        Case 109&: strReturn = "Complex Text"          'dbComplexText

        Case Else: strReturn = "Field type " & fld.Type & " unknown"

    End Select

    FieldTypeName = strReturn

End Function 

Thursday, October 27, 2011

Method using Excel to extract part string of delimited text

Excel really needs a string reverse or find last character or VB split() like function. In VB or VBA it's simple: 
 
To use Excel to extract part (usually the file name) of a path (or other string of delimited text) without resorting to VBA use the following:
Mid(path, InStrRev(path, "\")+1, Len(path))
=MID(B96,SEARCH(CHAR(222),SUBSTITUTE(B96,"\",CHAR(222),LEN(B96)-LEN(SUBSTITUTE(B96,"\",""))))+1,255)
The SUBSTITUTE function replaces all or a nth instance of a specific character. In this case a file path is being analyzed for the number of "\" characters. This is obtained by substituting "\" with nothing and subtracting the length of the shortened path string from the full path string. This provides the number of the last backslash. The SEARCH function finds this last substitution and provides the beginning of the file name for the MID function. CHAR(222) is used as replacement character because it cannot be in the path. Any unprintable character will do.
Replace "B96" with the cell address containing your text.
As and example:
       96
B    C:\my files\spreadsheets\2011\april\bnx.xls
Analyzing from inside out
SUBSTITUTE(B96,"\",""))
results in C:my filesspreadsheets2011aprilbnx.xls
LEN(SUBSTITUTE(B96,"\",""))
results in 38
LEN(B96)-LEN(SUBSTITUTE(B96,"\",""))
results in 43 - 38 or 5, the number of the last backslash
SUBSTITUTE(B96,"\",CHAR(222),LEN(B96)-LEN(SUBSTITUTE(B96,"\","")))
results in C:\my files\spreadsheets\2011\april[222]bnx.xls
SEARCH(CHAR(222),SUBSTITUTE(B96,"\",CHAR(222),LEN(B96)-LEN(SUBSTITUTE(B96,"\",""))))+1
results in 36
and, finally, the entire nesting
=MID(B96,SEARCH(CHAR(222),SUBSTITUTE(B96,"\",CHAR(222),LEN(B96)-LEN(SUBSTITUTE(B96,"\",""))))+1,255)
gives you bnx.xls

Thursday, October 21, 2010

Using anonymous pipes to redirect standard input/output of a child process

From: http://edn.embarcadero.com/article/10387 by mykle hoban


Note: This is a copy of an article that I did not want to lose so I copied it here. Mykle Hoban has a 'use freely' message so it's OK if you wish to use this in your code. It's based on C++ Builder using the Win32 platform.


In this article, I will explain how to spawn a console application and redirect its standard input/output using anonymous pipes. An anonymous pipe is a pipe that goes only in one direction (read pipe, write pipe, etc.). Maybe you are asking, "why would I ever need to do this sort of thing?" One example would be a Windows telnet server, where you spawn a shell and listen on a port and send and receive data between the shell and the socket client. (Windows does not really have a built-in remote shell).


First, we should talk about pipes. A pipe in Windows is simply a method of communication, often between process. The SDK defines a pipe as "a communication conduit with two ends; a process with a handle to one end can communicate with a process having a handle to the other end." In our case, we are using "anonymous" pipes, one-way pipes that "transfer data between a parent process and a child process or between two child processes of the same parent process." It's easiest to imagine a pipe as its namesake. An actual pipe running between processes that can carry data.


We are using anonymous pipes because the console app we are spawning is a child process. We use the CreatePipe function which will create an anonymous pipe and return a read handle and a write handle. We will create two pipes, on for stdin and one for stdout. We will then monitor the read end of the stdout pipe to check for display on our child process. Every time there is something availabe for reading, we will display it in our app. Consequently, we check for input in our app and send it off to the write end of the stdin pipe.




//------------Sample using CreateProcess and Anonymous Pipes-----------------
//---------------------childspawn.cpp----------------------------------------
//---------------------use freely--------------------------------------------
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#pragma hdrstop
#include <condefs.h>

#define bzero(a) memset(a,0,sizeof(a)) //easier -- shortcut

bool IsWinNT()  //check if we're running NT
{
  OSVERSIONINFO osv;
  osv.dwOSVersionInfoSize = sizeof(osv);
  GetVersionEx(&osv);
  return (osv.dwPlatformId == VER_PLATFORM_WIN32_NT);
}

void ErrorMessage(char *str)  //display detailed error info
{
  LPVOID msg;
  FormatMessage(
    FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    NULL,
    GetLastError(),
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
    (LPTSTR) &msg,
    0,
    NULL
  );
  printf("%s: %s\n",str,msg);
  LocalFree(msg);
}

//---------------------------------------------------------------------------
void main()
{
  char buf[1024];           //i/o buffer

  STARTUPINFO si;
  SECURITY_ATTRIBUTES sa;
  SECURITY_DESCRIPTOR sd;               //security information for pipes
  PROCESS_INFORMATION pi;
  HANDLE newstdin,newstdout,read_stdout,write_stdin;  //pipe handles

  if (IsWinNT())        //initialize security descriptor (Windows NT)
  {
    InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(&sd, true, NULL, false);
    sa.lpSecurityDescriptor = &sd;
  }
  else sa.lpSecurityDescriptor = NULL;
  sa.nLength = sizeof(SECURITY_ATTRIBUTES);
  sa.bInheritHandle = true;         //allow inheritable handles

  if (!CreatePipe(&newstdin,&write_stdin,&sa,0))   //create stdin pipe
  {
    ErrorMessage("CreatePipe");
    getch();
    return;
  }
  if (!CreatePipe(&read_stdout,&newstdout,&sa,0))  //create stdout pipe
  {
    ErrorMessage("CreatePipe");
    getch();
    CloseHandle(newstdin);
    CloseHandle(write_stdin);
    return;
  }

  GetStartupInfo(&si);      //set startupinfo for the spawned process
  /*
  The dwFlags member tells CreateProcess how to make the process.
  STARTF_USESTDHANDLES validates the hStd* members. STARTF_USESHOWWINDOW
  validates the wShowWindow member.
  */
  si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
  si.wShowWindow = SW_HIDE;
  si.hStdOutput = newstdout;
  si.hStdError = newstdout;     //set the new handles for the child process
  si.hStdInput = newstdin;
  char app_spawn[] = "d:\\winnt\\system32\\cmd.exe"; //sample, modify for your
                                                     //system

  //spawn the child process
  if (!CreateProcess(app_spawn,NULL,NULL,NULL,TRUE,CREATE_NEW_CONSOLE,
                     NULL,NULL,&si,&pi))
  {
    ErrorMessage("CreateProcess");
    getch();
    CloseHandle(newstdin);
    CloseHandle(newstdout);
    CloseHandle(read_stdout);
    CloseHandle(write_stdin);
    return;
  }

  unsigned long exit=0;  //process exit code
  unsigned long bread;   //bytes read
  unsigned long avail;   //bytes available

  bzero(buf);
  for(;;)      //main program loop
  {
    GetExitCodeProcess(pi.hProcess,&exit);      //while the process is running
    if (exit != STILL_ACTIVE)
      break;
    PeekNamedPipe(read_stdout,buf,1023,&bread,&avail,NULL);
    //check to see if there is any data to read from stdout
    if (bread != 0)
    {
      bzero(buf);
      if (avail > 1023)
      {
        while (bread >= 1023)
        {
          ReadFile(read_stdout,buf,1023,&bread,NULL);  //read the stdout pipe
          printf("%s",buf);
          bzero(buf);
        }
      }
      else {
        ReadFile(read_stdout,buf,1023,&bread,NULL);
        printf("%s",buf);
      }
    }
    if (kbhit())      //check for user input.
    {
      bzero(buf);
      *buf = (char)getche();
      //printf("%c",*buf);
      WriteFile(write_stdin,buf,1,&bread,NULL); //send it to stdin
      if (*buf == '\r') {
        *buf = '\n';
        printf("%c",*buf);
        WriteFile(write_stdin,buf,1,&bread,NULL); //send an extra newline char,
                                                  //if necessary
      }
    }
  }
  CloseHandle(pi.hThread);
  CloseHandle(pi.hProcess);
  CloseHandle(newstdin);            //clean stuff up
  CloseHandle(newstdout);
  CloseHandle(read_stdout);
  CloseHandle(write_stdin);
}
//----------------------------EOF--------------------------------------------
//---------------------------------------------------------------------------

Monday, September 27, 2010

Where the heck is My Uploads on a Virgin Phone?

A bit off topic:

I wanted to upload some pictures from my cell phone with a Virgin Mobile pay as you go account and an option was to place the pictures into My Uploads. I could not find out where on the internet this place was.

After two hours of searching and finally resorting to calling customer support and being on hold for 20 minutes I got it:


The site states that:

"You will need a PXT password though. Just take a picture and send it to your online album or share it with a friend from your Virgin Mobile phone; once you do either, you'll be prompted to create your PXT password. After that, use that PXT password here to sign in."

However, I was not prompted for anything. Aggravating.

This combined with all of the missing links in the web site, difficult to find help information, and general vagueness makes me certain that I will not be signing up for a long term contract.


Update: I discovered the free BitPim utility which allows me to download pictures from my Samsung SPH-m300 using an inexpensive USB data cable I purchased from CellularPlasa on eBay for $4.

Tuesday, September 21, 2010

Importing UNIX Data into Oracle on a Windows Server

Importing data into an Oracle database can be a challenge at times. The Oracle loader utility (sqlldr) can be a bit problematic and does not allow you to view the data prior to importing it.
Creating an external table has the advantage of allowing manipulation of the data as if it was an actual database table. However, like sqlldr, data that is even slightly nonconforming to the expected format will cause a myriad of problems. Three common formatting problem are incorrect line terminators, inconsistent quotes, and embedded commas.
When a comma delimited flat file is exported from a UNIX database and transferred for loading into a Windows based Oracle database the text lines are terminated with 0x0A in UNIX and not the 0x0A 0x0D pair utilized in the DOS/Windows OS. Although many technical text editors will correct this for you or a program could be quickly written to make the changes this is, as the example below shows, not necessary.
Another problem often encountered is that the fields are randomly double quoted, and some contain commas internal to the field's data. Here is an example of a typically messy format to import. This is from a real life situation I was working on. All of the data in the example is, of course, altered for privacy:

100001,"7123 HIGHLAND DR","03/28/1965"," Mercer Island WA 98040","Morgan, Mary A", "","","63034630752", 14,1,"F","T","06/28/2000","",0,"","01/01/1999","N", "01/01/1999",""

100020,"5432 SOUTH 28TH ST","01/01/1951"," Mercer Island WA, 98040", "Burgess,Frank", "","2566400",ZPW345070938,64,1,"M",B,"02/23/2000","",0,"", "12/31/1799","P","12/31/1979",""

The external table definition used to load this data is:

CREATE TABLE some_data (
ACCOUNT_NUM VARCHAR2(10),
ADDRESS VARCHAR2(30),
BIRTH_DATE VARCHAR2(10),
CITY_STATE_ZIP VARCHAR2(40),
NAME VARCHAR2(30),
EMPLOYER_NAME VARCHAR2(30),
GROUP_NAME VARCHAR2(30),
ID_NO VARCHAR2(50),
PLAN_NO VARCHAR2(50),
VAR_REC VARCHAR2(50),
SEX VARCHAR2(2),
TYPE VARCHAR2(10),
UPDATE_DATE VARCHAR2(10),
EXD_ID VARCHAR2(30),
PCP VARCHAR2(30),
PCP_EFFECT VARCHAR2(30),
CANCEL_DATE VARCHAR2(10),
COV_DEP VARCHAR2(30),
EFFECT_DATE VARCHAR2(10),
ADDL_INFO VARCHAR2(50))
ORGANIZATION EXTERNAL
(TYPE oracle_loader
DEFAULT DIRECTORY ext
ACCESS PARAMETERS
(
RECORDS DELIMITED BY 0x'0A'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
MISSING FIELD VALUES ARE NULL)
LOCATION ('UNIX_DATA.DAT'))
REJECT LIMIT unlimited;

desc some_data

set linesize 121
col account_num format a10
col address format a20
col birth_date format a20
col city_state_zip format a20
col name format a20

SELECT account_num, address, birth_date, city_state_zip, name
FROM some_data;

Defining the record access parameters with
RECORDS DELIMITED BY 0x'0A'
stops the Oracle loader from seeing the file as one single record due to expecting lines to be terminated in the same manner as the host OS. If the data is a large file that exceeds the half MB limit, Oracle loader would simply produce an error.
The record access parameters
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
allows fields like "Doe,Jane R" to be correctly interpreted as a single field instead of two containing "Doe and Jane R" while not rejecting fields that do not have quotes.

Going Virtual

VT Can Really Help - XP and Vista Together - With Linux

I was frustrated that it was problematic to run Ubuntu and Vista and XP on my laptop (I am a consultant and need to have the ability to develop in several platforms) when I realized that I could add Oracle VirtualBox into Linux (or Vista but I usually use Linux) and run XP. Note that only XP Pro SP3 (which I have) allows virtualization to run under it, not Home. Vista also does not allow running a virtual machine if it is the Home or Business version, only if Enterprise and Ultimate and it's the same for Windows 7.


XP runs so well on my HP 6830s with a P8400 Duo that I can't tell that it's a virtual machine. I'm thinking about creating a second virtual machine that runs Windows 7.


Oracle VirtualBox is a free product (with source code) that, as far as I have found, allows most popular OS versions to run as a virtual machine and allows multiple OS versions as the host. I would suggest using Linux as the host OS. It does not have restrictions and seems to run faster in my experience.

When virtualization becomes more mainstream maybe Microsoft will reconsider. Consumers are not going to pay for the highest cost OS just to sand box their kid's activities (my nephew trashed my mother's computer with downloads and games a while ago) if another OS (Linux most obviously) will allow you to do anything.

Microsoft's Requirements When Creating a Virtual Machine

Help and Discussion about virtualization at Intel

Oracle VirtualBox

How to Fix a Grub2 Selective Boot after Destroying It

This is a copy of an article. It is not plagiarized - I do not claim to have written it. This is simply copied here so I always have it as a reference and others may have it too. This was the best explanation I found and it took a long time to find it. The original posting in the forum is gone - as I expected. It has shown up in another forum now and it will also be gone at some point. I did not edit out the extraneous banter or correct the spelling in respect to the original author who is unknown but I thank him/her. 

From http://www.ubuntu-inside.me/2009/06/howto-recover-grub2-after-windows.html and now http://ubuntuforums.org/archive/index.php/t-1752186.html


I clobbered Grub - now what?

My Grand Unified Boot-loader was failing to be able to load Windows XP and I restored the Windows boot by copying the NTLDR and ntdetect.com from the install disk and using the fixmbr and fixboot commands. Now I can't boot Linux.

The problem probably was that the Ubuntu 10.04 LTS upgrade reset GRUB2.

I found this solution and it works:

Today i destroyed my Grub2 via installing windows on my notebook which i write blog posts.(I quit smoking , so i have to play some games : ) No rush). It may be hard to recover it since there are not much (i did not find anything) howtos around the net about recovering Grub2.Here is the step by step guide to recover it :

You will need a LIVE cd if you are going to recover an Ubuntu Box.Download Ubuntu Jaunty, Karmic whatever you want.Open the system with Live CD (I assume you are using Ubuntu Live CD).Press Alt+F2 and enter gnome-terminal command.And continue by entering :

$sudo fdisk -l

This will show your partition table.Here is my table to understand it better :

/dev/sda1 29 8369 66999082+ 83 Linux
/dev/sda2 * 8370 13995 45190845 7 HPFS/NTFS
/dev/sda3 13996 14593 4803435 5 Extended
/dev/sda5 13996 14593 4803403+ 82 Linux swap / Solaris

Now i will mount Linux (sda1 here), i have no external boot partition as you can see.(IF YOU HAVE external one, do not forget to mount it! )

$sudo mount /dev/sda1 /mnt
$sudo mount --bind /dev /mnt/dev
$sudo mount --bind /proc /mnt/proc

The following command is optional (it copies resolv.conf)

$sudo cp /etc/resolv.conf /mnt/etc/resolv.conf

Now chroot into the enviroment we made :

sudo chroot /mnt

After chrooting, you do not need to add sudo before your commands because from now, you will run commands as root.

You may want to edit /etc/default/grub file to fit your system (timeout options etc)

#nano -w /etc/default/grub

Play with the options if you want.(But do not forget to give grub-update command if you saved it ;) )

Now install/recover Grub2 via :

#grub-install /dev/sda

command.However you may get errors with that code like me.If so please use this command :

#grub-install --recheck /dev/sda

Now you can exit the chroot, umount the system and reboot your box :

#exit
$sudo umount /mnt/dev
$sudo umount /mnt/proc
$sudo umount /mnt
$sudo reboot