Microsoft Excel: Saving Currency Values in CSV Files

Missing digit in CSV File Currency Values

I searched all over and did not find the right answer so I played around until I had found it myself and now I’ll share it with you.

I had picked up a VBA coding project from a contractor who was leaving.
The application takes a bunch of Excel data in various complex formats and creates a comma separated value file (CSV) as output that are used for import into a custom object in Salesforce.

The application takes a bunch of Excel data in various complex formats and creates a comma separated value file (CSV) as output that are used for import into a custom object in Salesforce.

One of the issues the customer was reporting was that they had an expectation that currency value in exported CSV files should retain the 2 digits after the decimal point.

The application was sending the output result in a CSV file but the currency value were only retaining one digit after the decimal if the last decimal was a zero.

A value of 91.20 is coming out as 91.2, missing the 2nd digit.

This happens consistently while using Excel with CSV files but not with XLS. It drops the zero every time.

How to Save currency values in a CSV File using Microsoft Excel

  1. Save your CSV file in MS Excel format with a .xlsx or .xls extension.
  2. Update the “Format Cells” to display the column as Currency. I set the option not to display a $ for currency and 2 digits after the decimal point.
  3. Save this file with a .CSV extension.  Your currency values with the 2 digits after the decimal point are preserved during this conversion.

That is it!

To validate it, open the CSV file with a text editor like Notepad or Notepad++ and you will see the number format with 2 digits after the decimal was retained.

Watch out!  If you open the CSV file with MS Excel and save it again as CSV, Excel will wipe out your currency formatting. 

Hope this helps somebody. 

Arrow Functions in JavaScript / ES6

What are Arrow Functions in JavaScript / ECMAScript 6?

Arrow functions are a simplified short hand method for creating anonymous functions.

Older ES5 JavaScript example:

function setup() {
    createCanvas(600, 400);
    background(0);
    let button = createButton('press')
    button.mousePressed(changeBackground);

    function changeBackground() {
        background(random(255));
    }
}

New ES6 JavaScript Example

function setup() {
    createCanvas(600, 400);
    background(0);
    let button = createButton('press')
    button.mousePressed(() => background(255)));
}

Anonymous Function written as Arrow Function:

() => background(255))

Video: ES6 Arrow Function

How to Prevent Page Reload with Javascript onclick Without Using “#”

Just a very short blog jot for today as I’m busy working on a Salesforce.com data analysis tool / system.

Since this is a very low budget project, I’m hand coding everything using Notepad++ and VBScript to create a dynamic web application in old school Acitve Server Pages (ASP) that can consume the analytic data that my system stored on a MS Access database.

Again, low budget. Me and my wits pounding out code.

I ran in to an issue where the web page is calling a JavaScript that passes two variables to an Ajax call to load a Chart.js bar chart in a popup window. 

It was working great but the page that had the onlick event to load the popup chart was reloading as well when clicked instead of just loading the popup window with the chart.

What’s the fix?

Add “return false;” at the like in the example below and the page containing the onclick link should stop reloading when clicked.

Old Code

<a href="#" OnClick="getChart('John Smith', '2017-01');">John Smith</a>

New Code

<a href="" OnClick="getChart('John Smith', '2017-01');return false;">John Smith</a>

I always give credit to the people who helped me find the answer.  I had found the answer in the stackoverflow page below.

https://stackoverflow.com/questions/17680436/how-to-prevent-reload-with-onclick-without

Learn Python Quickly: My Crash Course in to Python Programming

Learning Python Quickly (How I did it!)

I had really been wanting to find a couple of hours to do a crash course on python and finally found an opportunity to do it last night.

Having been a programmer for a while, I’m finding many of the Python videos quite boring as they are for absolute beginners which has you plodding through the basics of programming which makes learning a new programming language painfully slow.

Luckily, I had found this great Python primer video by Derek Banas on YouTube.

I followed along and completed all the code in the video and had working examples of most of the important code snippets for much of how stuff gets done in Python.  Success was not without some pain, see problems and  resolutions below.

Video: Learn Python in One Video

I will save you some pain by telling you there is a very frustrating code issue at the end of this long video.

See code fix below.

When you are in the Dog class referencing the Animal super class from which it inherits the name, weight, height and sound, properties or values you have to use the setter and getter class methods instead of the object “self” reference like we could in the Animal object.  Once you change that, the code works.

For the Dog class, the toString() method only worked for me the example below:

(this works)
def toString(self):
        return "{} is {} cm tall and {} kilograms, says {} and owner is {}".format(
                                                                    self.get_name(),
                                                                    self.get_height(),
                                                                    self.get_weight(),
                                                                    self.get_sound(),
                                                                    self.__owner)
...instead of:
(this doesn't)
def toString(self):
        return "{} is {} cm tall and {} kilograms, says {} and owner is {}".format(
                                                                    self.__name,
                                                                    self.__height,
                                                                    self.__weight,
                                                                    self.__sound,
                                                                    self.__owner)

As a bonus I would add watching this video to help solidify the idea of polymorphism if you’re having difficulty getting a grasp of it.

Hope this helps someone!

Video: What is Polymorphism?

How to Make an Arduino Based Temperature Monitor with Code Example

I’m taking a class on how to build Arduino devices.

This morning’s Arduino lesson used a temperature sensor to read the temperature and output it to the Arduino serial monitor.


Here is my Arduino code which is a combo of code from the class plus my attempt to integrate an alarm to it which I got from a combination of YouTube and Arduino online resources.

The code is my own creation based on stuff I found. 

It is free for you to use copy improve and share.  Enjoy!

Arduino Based Temperature Monitor Sample Code

int tempPin = A1; //Sets Analog Pin for Temp Sensor
int buzzer = 12; //Set alarm pin for peiezio sensor
int alertTemp = 78;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600); //Starts serial monitor output
  pinMode(buzzer,OUTPUT); //initialize the buzzer pin as an output
  }

void loop() {
  // put your main code here, to run repeatedly:
  int sensorVal = analogRead(tempPin); //Reads the temperature sensor
  float voltage = sensorVal * 5; //Turns sensrVal to Voltage
  voltage /= 1024; //Divides voltage by 1024 and sets voltage to that result
  float tempC = (voltage - .5) * 100; //Determines temp inCelsius from Voltage
  float tempF = (tempC * 9 / 5)  + 32; //Determines temp in Fahrenheit from Celsius
  
  /***************** Begin Loop for alarm if temp is above ***********************/
   unsigned char i; //define a variable
   if(tempF > alertTemp) {
    Serial.println(tempF); //Prints value of tempF variable anbd end line
    Serial.print("Celsius Temp: "); //Prints Title
    Serial.print(tempC);
    Serial.print(" Fahrenheit Temp: "); //Prints Title
    delay(1000); //delay for 1000 milliseconds
  
      //output an frequency
      for(i=0;i<80;i++) {
        digitalWrite(buzzer,HIGH);
        delay(1);//wait for 1ms
        digitalWrite(buzzer,LOW);
        delay(1);//wait for 1ms;
       }
      //output another frequency
      for(i=0;i<100;i++){
        digitalWrite(buzzer,HIGH);
        delay(2);//wait for 2ms
        digitalWrite(buzzer,LOW);
        delay(2);//wait for 2ms;
       }
   }
   else {
    digitalWrite(buzzer,LOW);
   }
  /***************** End Loop for alarm if temp is above ***********************/
  
  Serial.println(tempF); //Prints value of tempF variable and end line
  Serial.print("Celsius Temp: "); //Prints Title
  Serial.print(tempC);
  Serial.print(" Fahrenheit Temp: "); //Prints Title
  delay(1000); //delay for 1000 milliseconds
}

HTTP 302 Redirects Instead of 404 Page Not Found Error in IIS URL Rewrite

SEO: Google Web Master Report Showing Multiple 302 Redirects Instead of 404 Page Not Found Error with IIS URL Rewrite and Web.Config File Fix

While reviewing my Google Web Master Reports, I came across an odd error.

When a 404, Page Not Found, error should be showing for a page that has since been deleted, multiple 302s are generated and Google errors out with something like Err too many redirects.

The root cause was a reconfiguration in Microsoft IIS.

I have a custom 404 error page configuration that references the execution of a customer 404 page for a better customer experience.

Below is what the configuration looks like in IIS. It is set to execute a custom 404.asp page instead of the standard 404.htm page included with IIS.

Let’s zoom out and see that there is another error setting.

If we click to edit the “Error” feature setting, we will see another we are editing the custom 404 setting.

This is the correct setting that solved the issue!  Scroll down to see the wrong configuration.

This is the wrong or bad or incorrect configuration that caused me some grief.

Hope this helps somebody!

Cheers!

Excel VBA: Delete all worksheets except for specific ones you want to retain

Excel VBA code example: How to delete all worksheets except for specific ones ones you want to retain.

This came from real world need today so I pounded this code out.

  • I have a list of worksheets that I want to keep. 
  • I run a process that adds analyzed data from another file to a new “temp” worksheet (tab), and additional worksheets per vendor from parsing out the imported data in the temp worksheet.
  • I can end up with 10 new tabs of analyzed data including the temp sheet.
  • The code below deletes all worksheets not on a “keep sheet list” . Basically, resetting the spreadsheet back to the state it was in before I ran macro to import and process that data that added all the new sheets.

Excel VBA Example: ResetWorkbook Function

'Reset Workbook
'Author Rick Cable
'Date: 11/28/2018
'Title: ResetWorkbook
'Purpose:   Deletes any workbooks not on the keep sheet list,
'           resetting it to previous state if you've added them
'           from some other process and want to undo
'Version 1.0
Public Sub ResetWorkbook()
    Dim sht As Worksheet
    Dim arrKeepSheetList As Variant
    Dim strKeepSheetList As Variant
    Dim isOnList As Boolean
        
    Application.DisplayAlerts = False
    isOnList = False
    strKeepSheetList = "Instructions,Template,Sample CSV File,Data Elements,Config"
    arrKeepSheetList = Split(strKeepSheetList, ",")
    
    'Loop through each worksheet in ActiveWorkbook
     For Each sht In ActiveWorkbook.Worksheets
        isOnList = False
        'Look thru list of sheets to keep
        'if sheet is not on list, delete it
        For Each Item In arrKeepSheetList
            If sht.Name = Item Then
                isOnList = True
                'If isOnList Then
                '    Exit For
                'End If
            End If
        Next Item
            
        If isOnList Then
             'MsgBox (sht.Name & " is on the list")
        End If            
        
        If isOnList = False Then
            'MsgBox ("I would be deleting " & sht.Name & " right now!")
            Worksheets(sht.Name).Delete
        End If
        
    Next sht

    Application.DisplayAlerts = True

End Sub

Excel VBA: Find Next Empty Cell in Row, When only Row 1 is Populated

It is Saturday and I’m about to go out and mow my lawn but I had an Excel VBA problem I wanted to get past first.

Not sure this is the best solution, but it is the one I came up with so I’ll share it with you now before I go mow.

Problem: I have a worksheet that is an event log for other subroutines. When created, the worksheet had a header row also created with data in Row 1 across columns A thru G.

When “WriteToEventLog” function call is firing off, we’re checking for next empty row in column A and cell “A1” already has header data written to it.

The method below works when cell “A2” is the starting cell, but fails when cell “A1” is used as the starting Range.

This code fails in an infinite loop and Out of Memory Error when starting from cell “A1” and ends up selecting all the cells memory can hold from column A.

        Range("A1").Activate
        ActiveCell.End(xlDown).Select
        ActiveCell.Offset(1, 0).Select

Here is my Saturday morning, pre mow, attempt to solve this issue with a custom Excel VBA function that correctly finds the next empty cell when row 1 contains the header row.

Please email me if you read this and it has helped you in any way.

1st Pass Code:

Sub AddItemToEndOfList()
    Worksheets("Sheet1").Activate
    'We assume that row 1 is already filled out from first process
    'Check to see if cell A2 is empty, if so then enter the next value in A2.
    'if not then go to next empty cell and enter the value.
    Range("A2").Activate
    If IsEmpty(Range("A2").Value) Then
        Range("A2").Activate
        ActiveCell.Value = "http://wwww.FinditClassifeds.com"
    Else
        Range("A1").Activate
        ActiveCell.End(xlDown).Select
        ActiveCell.Offset(1, 0).Select
        ActiveCell.Value = "http://wwww.FinditClassifeds.com"
    End If
End Sub

This can all be replaced with one line of code…

2nd Pass with Improved Code:

Range("A2").End(xlDown).End(xlUp).Offset(1, 0).Select

This works every time when we know Row 1 is already populated.

How to Setup a Cloud Hosted Web Server using Ubuntu and GOLang

Sometimes you want to learn something new but you don’t know where to start.

I’ve been working in the Microsoft world & IIS stack so long, I forget how many other web stacks are out there and how many have past us by. OMG!

I wanted to challenge myself to learn how to setup a Cloud hosted, Linux based, web server to host a website. 

Where do I start? Well, I just started looking stuff up and decided to blog my learning process along the way.

I hope this helps somebody else with the same desire to learn this topic as quickly and painlessly as possible.

Here we go…. 

First off, I was not sure what programming language I wanted to use.

This project will use a Linode cloud hosted server and Ubuntu as our operating system.

I started off thinking about a web server using a LAMP stack. I thought it would be OK to consider other options too.

I hate to assume people know tech jargon so I will stop and explain things as best I can along the way. My intent is to inform, not to annoy.

That being said, what is a stack? 

A stack is just a term used to describe a collection of all the moving parts required to have a modern functional website. Includes server operating system, web server software, a database and a framework/language.

When we hear the term, “Full Stack Developer”, someone is talking about a developer with experience in all of the aspects of the stack.
For example,a LAMP stack is Linux OS + Apache Web Server + MySQL Database + PHP language/framework. 

Other server options are replacing Apache with NginX (Pronounced engine x) or building our own using the GO programming language (GoLang). 

I started off with Apache then went to NginX.

I got NginX to work as a server so I’m leaving that information in this article at the bottom.

The rest of this article is focused on trying to get a very basic Linux Ubuntu + GOLang stack up and running.  No database.

If you and I can get a server up and running, I’m going to celebrate then come back later and add the database.

Let’s do this!

What you need before you start:
If you’re using Windows to connect to your Linux server, use Putty to connect via SSH. Details are in the video. Get Putty here.

SSH is a client/server program that enables secure connection to the SSH server on a remote machine. The SSH command is used for remote execution of login, file transfer between machines and executing other remote commands.
Step 1. Go to https://www.Linode.com and create an account. You will need a credit card. A web server can be run on Linode for about .50 a day so learning can be inexpensive. You can delete the server anytime and just rebuild it in about 5 minutes.

Once you have your account, watch and follow along with this video.

Video: Setting Up Your Linode Ubuntu Server

2. Once, You’ve installed your server. You should take some time to secure it. Watch this video and follow along.

Command line commands:
- sudo apt-get update
- sudo apt-get upgrade
- nano /etc/hosts

Video: Securing Your Linode Server

Command line commands:
- adduser example_user
- adduser example_user sudo
- ssh-keygen -b 4096 

Installing GO and Use It to Setup a Super Simple Web Server

I tried several times and failed to get Apache running quickly so I moved on to NginX and GOLang since this was supposed to be easy.
Use the two videos above to walk you thru building your server operating system and securing it.  
Then go to https://www.linode.com/docs/development/go/install-go-on-ubuntu/ to get the instruction on how to install GO on Ubuntu.
Then watch and follow along with this video to see how to write your own web server and web application using GO.  It has all the moving parts built in, you won’t believe how easy it is. Give it a try!

It may be working when you click. Here is a simple page up and running.

http://74.207.244.122:8000/about/

Video: Simple Web App – Go Lang Practical Programming Tutorial p.5

Lessons Learned

This is where I’ll stop and relate some Microsoft stack items to what we just did.

When we build sites on the Microsoft stack, we already have a server, Internet Information Services (IIS), it runs on Windows as a background service.  If we’re having issues or make an update to the web.config file, we restart the IIS service.

Once you get your simple server working, you will realize it is only running when you run it at the command line.  If you exit, your server stops.

We can cheat a bit by using the Linux command nohup which is short for “No Hangups”.

We don’t run nohup by itself, it is a supplemental command that tells Linux not to stop the main command, even if the user logs out. 

I won’t cover nohup in detail here I recommend this resource for learning more about the nohup command

We can fool our friends in to thinking we’ve built our server by launching our GO app using nohup but this is not the right way to do this. It needs to be running as a background service (daemon).

You will want to learn how to make your GO server run in the background all the time and restart automatically if the server restarts.

I was stuck on this part for a day or so but thank goodness I know the founder of chapbook.com, who is using GOLang so I hit them up and was  pointed to the link below.

https://fabianlee.org/2017/05/21/golang-running-a-go-binary-as-a-systemd-service-on-ubuntu-16-04/

This link is a good example of how to setup a background service called a daemon in Linux terms. 

We’ll do the example on the site above as an exercise to get some daemon code working before we move on to the next task of making our own server code run as a daemon.

It took me a while to get it working but I did. Took longer than I expected as I’m not used to doing everything remotely via SSH and with the nano text editor.  Feels like bootcamp.

Here is a screenshot of my successful attempt.

Installing NginX Web Server on Ubuntu

This is an older part of blog where I was going to go the NginX route and had a simple working server up. I’ll come back to update this later.

I started trying to do this using a LAMP stack but had too many problems getting the Apache2 web server to work and was getting tired and frustrated.

After staying up until 0230 failing to get the Apache2 server installed on Ubuntu, exhaustion was setting in and I was feeling the need a confidence boost so I started my Linode server over from scratch and tried this video on getting NginX web server installed and had success. Use this video first, it was much easier.

Video: Setting up a Linode Server and Hosting a Website using NginX Web Server

As next step, I followed these instructions to install the GO language and get a hello world app to work.

Next we figure out how to use GO to handle business logic on the web server.

Exporting Your Outlook Calendar to an Excel Spreadsheet

I’ve have not posted for a while. Partly because I’ve been so busy but I’ve also not had anything worthy of a blog post…. Until today.

THE CHALLENGE: Exporting your calendar to Excel and getting a calculated value for the duration of each task or appointment.

I did not design the code and the author did not provide their name in it so I could thank them or give them credit. I’ve tested this and it works great!

If you are not in to coding then scroll down to the very bottom of this post where I’ve embedded a video (not mine) of how to export Outlook calendars to Excel using the built in export feature in Outlook. The only issue I have with it is that is does not calculate the duration of the appointments and why I used the script below.  Good luck.

Sample Output:

I’ve made a couple of small modifications to the original script:
1. Changed the destination drive from d:\ to c:\
2. Changed the output date format from dd/mm/yyyy to mm/dd/yyyy

How to make this work?

First, you will need to be on a PC running Windows XP or Windows 7.

Copy the code below and paste it in to any text editor like notepad and save it with a file name like OutlookCalendarExport.vbs.

Now you should be able to double click the file you just saved to run this script. You should get message boxes asking for the start and end dates then it will tell you that it is going off to do its work and will display a message letting you know when it is done. It will save the file on the root of you c: drive.

VBScript: Export Outlook Calendar to Excel

on error resume next

DefaultDrive = "c:\"

set objFS=Wscript.CreateObject("Scripting.FileSystemObject")
If Not objFS.DriveExists(DefaultDrive) Then 
 msgbox "Your home drive is not mapped. (" & DefaultDrive & ")" & vbcrlf &  "Please resolve this before continuing.",48,"Drive Mapping Error"
 wscript.quit
end if


starttime = now

' ==== Define Variables
Dim theApp, theNameSpace, theMailItem, calendar, apointmentItems, appointment

' ==== Create a Shell and FileSystem Object
Set WshShell = WScript.CreateObject( "WScript.Shell" )
Set fso = CreateObject("Scripting.FileSystemObject")

' ==== Create Excel Spreadsheet
Set app = CreateObject("Excel.Application")
Set wb = app.Workbooks.Add
app.Visible = False
app.AlertBeforeOverwriting = True
wb.Activate
Row = 1
Column = 1
Set ws = wb.Worksheets(1)
ws.Cells(1,1).Value = "Subject"
ws.Columns(1).ColumnWidth = 50
ws.Cells(1,2).Value = "Date"
ws.Columns(2).ColumnWidth = 9
ws.Cells(1,3).Value = "Start Time"
ws.Columns(3).ColumnWidth = 11
ws.Cells(1,4).Value = "End Time"
ws.Columns(4).ColumnWidth = 11
ws.Cells(1,5).Value = "Duration"
ws.Columns(5).ColumnWidth = 8
ws.Cells(1,6).Value = "Category"
ws.Columns(6).ColumnWidth = 25
Row = Row + 1
OutputFileName = DefaultDrive & "OutlookExport.xls"

' ==== Connect to Outlook
set theApp = CreateObject("Outlook.Application")
set theNameSpace = theApp.GetNameSpace("MAPI")
set calendar = theNameSpace.GetDefaultFolder(9)
set appointmentItems = calendar.Items

' ==== Work out example dates
' ==== Selects Monday to Friday of Previous week as defaults
LastWeek = Weekday(Now)
Select Case LastWeek
 Case "1"
  Lastweek=Dateadd("D",-6,Date)
 Case "2"
  Lastweek=Dateadd("D",-7,Date)
 Case "3"
  Lastweek=Dateadd("D",-8,Date)
 Case "4"
  Lastweek=Dateadd("D",-9,Date)
 Case "5"
  Lastweek=Dateadd("D",-10,Date)
 Case "6"
  Lastweek=Dateadd("D",-11,Date)
 Case "7"
  Lastweek=Dateadd("D",-12,Date)
End Select

StartofRange = inputbox("Start of Range","Outlook Exporter",LastWeek)
StartofRange=cdate(StartofRange)
EndofRange = inputbox("End of Range","Outlook Exporter",(dateadd("D",+6,LastWeek)))
EndofRange=cdate(EndofRange)
DatetoQuery=StartofRange
 
If (StartofRange = "") OR (EndofRange = "") Then
 wscript.quit
End If

'Popup
Set Shell = wScript.CreateObject("wScript.Shell")
Msg = "Export Started. You will be notified when it is ready."
 Timeout = 5 
temp = Shell.Popup(Msg, Timeout, "Outlook Exporter") 
'if temp = -1 then
' wScript.Quit
'End if





' ==== Create Array to hold valid date range 
dim datearray()
numberofdays=DateDiff("D", StartofRange, (dateadd("D",1,EndofRange)))
If Not numberofdays = 0 Then 
 redim datearray(numberofdays)
Else
 redim datearray(1)
End if

' ==== populate the array with the valid dates for the query
datearray(0)=StartofRange
for x=1 to (ubound(datearray)-1)
 datearray(x)=dateadd("D",1,(datearray(x-1)))
next

' ==== Find appointments that occur between the StartofRange and EndofRange dates
set appointment = appointmentItems.GetFirst

While TypeName(appointment) <> "Nothing"
 if TypeName(appointment) = "AppointmentItem" then
  For x = 0 to ubound(datearray)
   ' ==== Check if appointment is recurring
   If appointment.RecurrenceState = 0 then
   ' =================================================================================
   ' ====================== Non Recurring appointment ================================
   ' =================================================================================
     If DateValue(appointment.Start) = datearray(x) then
      if (appointment.BusyStatus = 2) AND (appointment.sensitivity = 0) Then
       Beginning=TimeValue(appointment.start)
       Ending=TimeValue(appointment.end)
       Duration=DateDiff("N",Beginning,Ending)
       if appointment.AllDayEvent then
        Beginning="8:30:00"
        Ending="5:00:00"
        Duration="480"
       Else If Duration = "24.00" Then
        Duration="480"
       End If
       End If
       
       ws.Cells(Row,Column).Value = appointment.subject
       Column = Column + 1
       ws.Cells(Row,Column).Value = DatePart("M",appointment.start) & "/" & DatePart("D",appointment.start) & "/" & DatePart("YYYY",appointment.start)
       Column = Column + 1
       ws.Cells(Row,Column).Value = Beginning
       Column = Column + 1
       ws.Cells(Row,Column).Value = Ending
       Column = Column + 1
       ws.Cells(Row,Column).Value = FixDate(abs(Duration))
       Column = Column + 1
       ws.Cells(Row,Column).Value = appointment.categories
       Row = Row + 1
       Column = 1
      End If
     End If
   Else
   ' =================================================================================
   ' ====================== Recurring appointment -- get occurrence for date =========
   ' =================================================================================

    Set recurrencePattern  = appointment.GetRecurrencePattern
 
    Set recurringApptItem = recurrencePattern.GetOccurrence(datearray(x) & " " & TimeValue(appointment.Start))
    errorCode = err.Number
    err.Clear
    If errorCode = 0 then
    set appointment = recurringApptItem
     if (appointment.BusyStatus = 2) AND (appointment.sensitivity = 0) Then
      Beginning=TimeValue(appointment.start)
      Ending=TimeValue(appointment.end)
      Duration=DateDiff("N",Beginning,Ending)
      if appointment.AllDayEvent then
       Beginning="8:30:00"
       Ending="5:00:00"
       Duration="480"
      Else If Duration = "24.00" Then
       Duration="480"
      End If
      End If
      ThisOne = """" & appointment.subject & """" & "," & DatePart("M",appointment.start) & "/" & DatePart("D",appointment.start) & "/" & DatePart("YYYY",appointment.start) & "," & Beginning & "," & Ending & "," & Duration & "," & """" & appointment.categories & """" & vbCRLF
      If Not ThisOne = LastOne Then
       ws.Cells(Row,Column).Value = appointment.subject
       Column = Column + 1
       ws.Cells(Row,Column).Value = DatePart("M",appointment.start) & "/" & DatePart("D",appointment.start) & "/" & DatePart("YYYY",appointment.start)
       Column = Column + 1
       ws.Cells(Row,Column).Value = Beginning
       Column = Column + 1
       ws.Cells(Row,Column).Value = Ending
       Column = Column + 1
       ws.Cells(Row,Column).Value = FixDate(abs(Duration))
       Column = Column + 1
       ws.Cells(Row,Column).Value =  appointment.categories
       Row = Row + 1
       Column = 1
      LastOne = ThisOne
      End If
     End If ' status
    
    End If ' error code 0
   End If ' appointment.RecurrenceState = 0
  Next ' in date array
 End If ' TypeName(appointment) = "AppointmentItem"

set appointment = appointmentItems.GetNext
Wend

Beginning = ""
Ending = ""
Duration = ""

' =======================================================================================================
' ============================= Edited Recurring Appointments ===========================================
' =======================================================================================================

Set appointmentItems = calendar.Items
Set appointment = appointmentItems.GetFirst
While TypeName(appointment) <> "Nothing"
If TypeName(appointment) = "AppointmentItem" then
Set RecurrencePattern = appointment.GetRecurrencePattern
 Counter = RecurrencePattern.Exceptions.Count
 For y = 1 to (Counter)
 Set Exception = RecurrencePattern.Exceptions.Item(y)
  If (appointment.BusyStatus = 2) AND (appointment.sensitivity = 0) Then 
   For x = 0 to ubound(Datearray)
    If Formatdatetime(DatePart("M",Exception.AppointmentItem.start) & "/" & DatePart("D",Exception.AppointmentItem.start) & "/" & DatePart("YYYY",Exception.AppointmentItem.start)) = formatdatetime(Datearray(x)) Then
     Beginning=TimeValue(Exception.AppointmentItem.start)
     Ending=TimeValue(Exception.AppointmentItem.end)
     Duration=DateDiff("N",Beginning,Ending)
     If NOT Beginning = "" Then
      ws.Cells(Row,Column).Value = Exception.AppointmentItem.Subject
      Column = Column + 1
      ws.Cells(Row,Column).Value = DatePart("M",Exception.AppointmentItem.Start) & "/" & DatePart("D",Exception.AppointmentItem.Start) & "/" & DatePart("YYYY",Exception.AppointmentItem.Start)
      Column = Column + 1
      If Beginning = Ending Then
       Beginning="8:30:00 AM"
       Ending="5:00:00 PM"
       Duration="480"
      End If
      ws.Cells(Row,Column).Value = Beginning
      Column = Column + 1
      ws.Cells(Row,Column).Value = Ending
      Column = Column + 1
      If (Duration = "24.00") OR (Duration = "0") Then
       Duration="480"
      End If
      ws.Cells(Row,Column).Value = FixDate(abs(Duration))
      Column = Column + 1
      ws.Cells(Row,Column).Value =  Exception.AppointmentItem.categories
      Row = Row + 1
      Column = 1
      Beginning = ""
      Ending = ""
      Duration = ""
     End If
    End If ' date within range
   Next ' ubound(Datearray)
  End If ' appointment status
 Next 'y - counter
 set appointment = appointmentItems.GetNext
End If
wend

' =======================================================================================================
' =======================================================================================================
' =======================================================================================================


Function FixDate(Duration)
 Hours = fix(duration / 60)
 If (duration - (Hours*60)) < 10 Then
  Minutes = "0" & (duration - (Hours*60))
 Else
  Minutes = (duration - (Hours*60))
 End If
 If (Hours > 0) AND (Hours < 10) Then 
  T = "0" & Hours & ":" & Minutes
 Else If (Hours > 9 ) Then
  T = Hours & ":" & Minutes
 Else
  T = "00:" & Minutes
 End if
 End If
FixDate = T
End Function

' =============================================================
' ============ Sort Excel Data and save file ==================
' =============================================================
Const xlAscending = 1
Const xlDescending = 2
Const xlGuess = 0
Const xlTopToBottom = 1

app.Selection.Sort app.Worksheets(1).Range("F2"), _
                   xlAscending, _
                   app.Worksheets(1).Range("B2"), _
                   , _
                   xlAscending, _
                   , _
                   , _
                   xlGuess, _
                   1, _
                   False, _
                   xlTopToBottom

app.DisplayAlerts = False
err.clear
ws.SaveAs OutputFileName
If err.number = 1004 Then
 msgbox err.description & vbcrlf & vbcrlf & "Outlook Exporter cannot continue."
 err.clear
 wscript.quit
End If

wscript.sleep 2000

' ==== Tidy Up
set appointmentItems = nothing
set calendar = Nothing
theNameSpace.Logoff
set theNameSpace = Nothing
set theApp = Nothing
set fso = Nothing

Set ws = Nothing
wb.close
Set wb = Nothing


' ==== Exit Excel - important if visible = false
app.quit
set app = Nothing

endtime = now
TimeToComplete =  formatdatetime(endtime - starttime)

' ==== Inform that the report is finished
Retval = msgbox("Report duration was " & TimeToComplete & vbcrlf & vbcrlf & "View results now?",vbyesno + vbinformation,"Outlook Exporter")
If Retval = vbYes then 
 wsHshell.Run (OutputFileName)
End If
Set WshShell = Nothing

' ===========================================================================================================
' ================================================== Notes ==================================================
' ===========================================================================================================

' Lists appointments from Outlook Calander
' Includes only those appointments in user specified date range
' Includes regular , recurring and edit-recurring appointments 
' Only lists appointments where Busy and not Private
' If alldayevent then duration listed as 8 hours, start 8.30, stop 5pm
' If 24 hour event the duration listed as 8 hours, start 8.30, stop 5pm
' Converts duration to HH:MM format
' Sorts by Date, then start time
' Saves as H:\OutlokExport.xls

Video: Export Outlook Calendar to Excel