CSV to Named Sheets within existing Workbook - vba

So I’ve been at this for a while now and I’ve got various methods (some VBA, others PowerShell) semi working per say…
Quick overview of what I’m trying to accomplish is importing two CSV’s weekly (erasing the old data, headers always remain the same but are different between the two sheets) into two specific Excel Worksheets within the same workbook (Ex sheet1, sheet2, calculation sheet) which has another sheet that then calculates the data. Finally I’d like to export it as PDF.
Full Explanation:
Every Monday two queries are exported as .CSV to let’s say C:\Users\Me\Desktop\Data1.CSV & C:\Users\Me\Desktop\Data2.CSV
I would then like to have the CSV’s data input into their respective worksheets (Data1, Data2) within the workbook C:\Users\Me\Desktop\Calculation.xlsx
Data1 would look like this:
COUNT STATUS OPERATOR PRODUCT WEEK
1 CANCEL BOB Product 1 10
65 CLEAR JIM Product 2 10
20 SEND BOB Product 1 10
58 CC KRIS Product 4 10
3 CLEAR BOB Product 1 10
11 SEND SMIT Product 6 10
6 CANCEL JASON Product 7 10
Data2 would look like this:
OPERATOR CLEARS SENDS TOTAL CR WEEK
BOB 11 1 12 0.916667 10
JIM 17 2 19 0.894737 10
KRIS 9 1 10 0.9 10
SMITH 22 5 27 0.814815 10
JASON 25 7 32 0.78125 10
The calculation sheet will then recognize the data and process accordingly then export as a PDF. The following Monday, the windows timer service calls a .bat file which then runs this script (VBA or PowerShell) which erases the previous weeks data within this workbook and inputs the new data from the queries.
I’m very open as which language this is written in I have basic knowledge and understating of both PowerShell and VBA. I have not included the code I currently have as I’ve butchered it to try and get it to work within my needs as I have mixed together various methods from researching how to do this.
Hopefully I’ve provided enough information so that someone can point me in the right direction…
Thanks
EDIT
As per Chris here's some code I've been trying to utilize, it's probably extremely confusing as I was trying to modify it for my needs versus what it was initially made for:
Get-service bits | Select-Object COUNT, STATUS, OPERATOR, PRODUCT, WEEK | Export-Csv 'C:\Users\Me\Desktop\Data1.csv' -NoTypeInformation
Get-service bits | Select-Object COUNT, STATUS, OPERATOR, PRODUCT, WEEK | Export-Excel 'C:\Users\Me\Desktop\Calculations.xlsx'
$Results = #()
Import-Excel -Path 'C:\Users\Me\Desktop\Calculations.xlsx' | foreach {
$Properties = #{
COUNT = $PSItem.COUNT
STATUS = $PSItem.STATUS
OPERATOR = $PSItem.OPERATOR
PRODUCT = $PSItem.PRODUCT
WEEK = $PSItem.WEEK
}
$Results += New-Object -TypeName psobject -Property $Properties
}
Import-Csv -Path 'C:\Users\Me\Desktop\Data1.csv' | foreach {
$Properties = #{
COUNT = $PSItem.COUNT
STATUS = $PSItem.STATUS
OPERATOR = $PSItem.OPERATOR
PRODUCT = $PSItem.PRODUCT
WEEK = $PSItem.WEEK
}
$Results += New-Object -TypeName psobject -Property $Properties
}
$Results | Export-Excel -Path 'C:\Users\Me\Desktop\Calculations.xlsx'
Another example I tried:
$Excel = New-Object -ComObject Excel.Application
$XLSFile = 'C:\Users\me\Desktop\Calculations.xlxs'
$csvFile = 'C:\Users\me\Desktop\Data\Data1.csv'
$Excel.Visible = $true
$ExcelWorkBook = $Excel.Workbooks.Open($XLSFile)
$ExcelWorkSheet = $ExcelWorkBook.sheets.item('Data')
$ExcelWorkSheet.Activate()
# Go to the first empty row
$LastRow = $ExcelWorkSheet.UsedRange.rows.count + 1
Import-Csv -Path $csvFile | ForEach {
$ExcelWorkSheet.cells.Item($lastRow,1) = $psitem.COUNT
$ExcelWorkSheet.cells.Item($lastRow,2) = $psitem.STATUS
$ExcelWorkSheet.cells.Item($lastRow,3) = $psitem.OPERATOR
$ExcelWorkSheet.cells.Item($lastRow,4) = $psitem.PRODUCT
$ExcelWorkSheet.cells.Item($lastRow,5) = $psitem.WEEK
$LastRow = $LastRow + 1
}
$ExcelWorkBook.Save()
$ExcelWorkBook.Close()
$Excel.Quit()
$path = "C:\Users\me\Desktop\"
$xlFixedFormat = “Microsoft.Office.Interop.Excel.xlFixedFormatType” -as [type]
$excelFiles = Get-ChildItem -Path $path -include *.xls, *.xlsx -recurse
$objExcel = New-Object -ComObject excel.application
$objExcel.visible = $false
foreach($wb in $excelFiles)
{
$filepath = Join-Path -Path $path -ChildPath ($wb.BaseName + " Weekending " +(Get-Date).AddDays(-1).ToString('MMM-dd-yyyy') + “.pdf”)
$workbook = $objExcel.workbooks.open($wb.fullname, 3)
$workbook.Saved = $true
“saving $filepath”
$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $filepath, 1, 2)
$objExcel.Workbooks.close()
}
$objExcel.Quit()
Final example
$Excel = New-Object -ComObject Excel.Application
$XLSFile = 'C:\Users\me\Desktop\Calculations.xlsx'
$csvFile = 'C:\Users\me\Desktop\Data1.csv'
$Excel.Visible = $true
$ExcelWorkBook = $Excel.Workbooks.Open($XLSFile)
$ExcelWorkBook.worksheets.item("Data1").Delete()
# Create a new worksheet
$ExcelWorkSheet = $ExcelWorkBook.Worksheets.Add()
# Set the name for the worksheet
$ExcelWorkSheet.Name = "Data1"
$ExcelWorkSheet = $ExcelWorkBook.sheets.item('Data1')
$ExcelWorkSheet.Activate()
# Go to the first empty row
Import-Csv -Path $csvFile | ForEach {
$ExcelWorkSheet.cells.Item($lastRow,1) = $psitem.COUNT
$ExcelWorkSheet.cells.Item($lastRow,2) = $psitem.STATUS
$ExcelWorkSheet.cells.Item($lastRow,3) = $psitem.OPERATOR
$ExcelWorkSheet.cells.Item($lastRow,4) = $psitem.PRODUCT
$ExcelWorkSheet.cells.Item($lastRow,5) = $psitem.WEEK
$LastRow = $LastRow + 1
}
$ExcelWorkBook.Save()
$ExcelWorkBook.Close()
$Excel.Quit()

Filling the target cells one by one using Excel COM interface is very slow. Using Range.Copy and Sheet.Paste is much faster:
step 1: "ExcelApp.Workbooks.OpenText" to open data.csv file
step 2: "ExcelApp.Windows("data").ActiveSheet" to target the data sheet just opened
step 3: "ActiveSheet.Range" to target the data area
step 4: "Range.Copy" to copy the source data to the clipboard
step 5: "Sheet.Paste" to move the data to the destination
C++ version sample code(suppose we already have "pSheetCsvDest" which is our .csv data destination sheet) :
//import Excel library
//....
//declaration
Excel::_ApplicationPtr pAppExcel;
Excel::_WorksheetPtr pSheetCsvSource;
Excel::RangePtr pRangeCsvSource;
//initialze excel app and open the source csv file
HRESULT hApp = pAppExcel.CreateInstance(__uuidof(Excel::Application));
pAppExcel->Workbooks->OpenText(_bstr_t("data.csv"), 936, 1, xlDelimited, xlTextQualifierDoubleQuote, vtMissing, vtMissing, vtMissing, VARIANT_TRUE, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing);
//target the data range
pSheetCsvSource= pAppExcel->Windows->GetItem(_bstr_t("data"))->ActiveSheet;
pRangeCsvSource= pSheetCsvSource->GetRange(_variant_t("Cell1:Cell2"));
//copy the source data
pRangeCsvSource->Copy();
//paste to destination sheet
pSheetCsvDest->Paste();
//close csv source child window
pAppExcel->Windows->GetItem(_bstr_t("data"))->Close();

Related

write data into excel using powershell from sql table

I have a .xlsx file which was made into data table by oledb provider.Now I want to add value to that .xlsx based on the sql table data I have
(which is also converted into a csv file Book1.csv)
The sql table consists of name and notes...
Where name column is same in both .xlsx file and sql variable $sql
I want to add that close notes to f column of .xlsx file if the value of name matches with the value of sql table "A" column One I wrote below is very slow and not effective.
Any help would be highly appreciated.
$Excel = New-Object -ComObject Excel.Application
$Workbook = $Excel.Workbooks.Open('C:\Users\VIKRAM\Documents\Sample - Superstore.xlsx')
$workSheet = $Workbook.Sheets.Item(1)
$WorkSheet.Name
$Found = $WorkSheet.Cells.Find('$Data.number')
$Found.row
$Found.text
$Excel1 = New-Object -ComObject Excel.Application
$file = $Excel1.Workbooks.Open('C:\Users\VIKRAM\Documents\Book1.xlsx')
$ff=$file.Sheets.Item(1)
$ff.Name
$ff1=$ff.Range("A1").entirecolumn
$ff1.Value2
foreach ($line in $ff1.value2){
if( $found.text -eq $line)
{
Write-Host "success"
$fff=$ff1.Row
$WorkSheet.Cells.item($fff,20) =$ff.cells.item($fff,2)
}
}
Data in .xlsx file
Number Priority Comment
612721 4 - High
Data in Book1.csv
Number Clo_notes
612721 Order has been closed
I need to update clo_notes value to comment in .xlsx file if this "number" column in each file matches update the clos_notes to the corresponding column of comment
It looks like you answered my question about where "Nebraska" falls into the data.
Make sure to release any COM objects, or you'll have orphaned Excel processes.
You might try something like this. I was able to write the Clo_notes value into column 6 as you were requesting:
## function to close all com objects
function Release-Ref ($ref) {
([System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) -gt 0)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
## open Excel data
$Excel = New-Object -ComObject Excel.Application
$Workbook = $Excel.Workbooks.Open('C:\Users\51290\Documents\_temp\StackOverflowAnswers\Excel.xlsx')
$workSheet = $Workbook.Sheets.Item(1)
$WorkSheet.Name
## open SQL data
$Excel1 = New-Object -ComObject Excel.Application
$file = $Excel1.Workbooks.Open('C:\Users\51290\Documents\_temp\StackOverflowAnswers\SQL.xlsx')
$sheetSQL = $file.Sheets.Item(1)
$dataSQL = $sheetSQL.Range("A1").currentregion
$foundNumber = 0
$row_idx = 1
foreach ($row in $WorkSheet.Rows) {
"row_idx = " + $row_idx
if ($row_idx -gt 1) {
$foundNumber = $row.Cells.Item(1,1).Value2
"foundNumber = " + $foundNumber
if ($foundNumber -eq "" -or $foundNumber -eq $null) {
Break
}
foreach ($cell in $dataSQL.Cells) {
if ($cell.Row -gt 1) {
if ($cell.Column -eq 1 -and $cell.Value2 -eq $foundNumber) {
$clo_notes = $sheetSQL.Cells.Item($cell.Row, 2).Value2
Write-Host "success"
$WorkSheet.Cells.item($row_idx, 6).Value2 = $clo_notes
}
}
}
}
$row_idx++
}
$Excel.Quit()
$Excel1.Quit()
## close all object references
Release-Ref($WorkSheet)
Release-Ref($WorkBook)
Release-Ref($Excel)
Release-Ref($Excel1)

SQL extract data to Excel using Powershell

I want to extract data from SQL server to a new excel file using powershell . For small data set my code works but some tables has more than 100.000 rows and this will take ages. The reason why I don't use the utility in SQl server is because I want to extract mutilple tables.
Is there a way to optimize my script to export big tables to excel? or is there another way to do this?
I'm using the following script
## ---------- Working with SQL Server ---------- ##
## - Get SQL Server Table data:
$SQLServer = 'server';
$Database = 'database';
$SqlQuery = #' Select top 10 * from database.dbo.table '#;
## - Connect to SQL Server using non-SMO class 'System.Data':
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection;
$SqlConnection.ConnectionString = `
"Server = $SQLServer; Database = $Database; Integrated Security = True";
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand;
$SqlCmd.CommandText = $SqlQuery;
$SqlCmd.Connection = $SqlConnection;
## - Extract and build the SQL data object '$DataSetTable':
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$SqlAdapter.SelectCommand = $SqlCmd;
$DataSet = New-Object System.Data.DataSet;
$SqlAdapter.Fill($DataSet);
$DataSetTable = $DataSet.Tables["Table"];
## ---------- Working with Excel ---------- ##
## - Create an Excel Application instance:
$xlsObj = New-Object -ComObject Excel.Application;
## - Create new Workbook and Sheet (Visible = 1 / 0 not visible)
$xlsObj.Visible = 0;
$xlsWb = $xlsobj.Workbooks.Add();
$xlsSh = $xlsWb.Worksheets.item(1);
## - Build the Excel column heading:
[Array] $getColumnNames = $DataSetTable.Columns | Select ColumnName;
## - Build column header:
[Int] $RowHeader = 1;
foreach ($ColH in $getColumnNames)
{
$xlsSh.Cells.item(1, $RowHeader).font.bold = $true;
$xlsSh.Cells.item(1, $RowHeader) = $ColH.ColumnName;
$RowHeader++;
};
## - Adding the data start in row 2 column 1:
[Int] $rowData = 2;
[Int] $colData = 1;
foreach ($rec in $DataSetTable.Rows)
{
foreach ($Coln in $getColumnNames)
{
## - Next line convert cell to be text only:
$xlsSh.Cells.NumberFormat = "#";
## - Populating columns:
$xlsSh.Cells.Item($rowData, $colData) = `
$rec.$($Coln.ColumnName).ToString();
$ColData++;
};
$rowData++; $ColData = 1;
};
## - Adjusting columns in the Excel sheet:
$xlsRng = $xlsSH.usedRange;
$xlsRng.EntireColumn.AutoFit();
## ---------- Saving file and Terminating Excel Application ---------- ##
## - Saving Excel file - if the file exist do delete then save
$xlsFile = `
"C:\path\file.xls";
if (Test-Path $xlsFile)
{
Remove-Item $xlsFile
$xlsObj.ActiveWorkbook.SaveAs($xlsFile);
}
else
{
$xlsObj.ActiveWorkbook.SaveAs($xlsFile);
};
## Quit Excel and Terminate Excel Application process:
$xlsObj.Quit(); (Get-Process Excel*) | foreach ($_) { $_.kill() };
## - End of Script - ##
There's some simple magic to make this a lot easier, and that's Copy/Paste. What you can do is convert your datatable to a tab delimited CSV, copy that to the clipboard, and paste it into Excel. I'll ignore your SQL part, since you seem to have that well in hand.
## ---------- Working with Excel ---------- ##
## - Create an Excel Application instance:
$xlsObj = New-Object -ComObject Excel.Application;
## - Create new Workbook and Sheet (Visible = 1 / 0 not visible)
$xlsObj.Visible = 0;
$xlsWb = $xlsobj.Workbooks.Add();
$xlsSh = $xlsWb.Worksheets.item(1);
## - Copy entire table to the clipboard as tab delimited CSV
$DataSetTable | ConvertTo-Csv -NoType -Del "`t" | Clip
## - Paste table to Excel
$xlsObj.ActiveCell.PasteSpecial() | Out-Null
## - Set columns to auto-fit width
$xlsObj.ActiveSheet.UsedRange.Columns|%{$_.AutoFit()|Out-Null}

How to match SQL results with AD results and the other way around

I have to write a script that will query AD for users from a specific department that I will prompt user to input initially and then to output the software that is installed on the all machines that a specific user from the chosen department. I want to be able to see all the software from all the machine(s) that specific is logging on.
Presently I am using the following parts:
1) A query in AD to get the users from a specific department:
Import-Module ActiveDirectory
$Dept = Read-Host "Enter the desired department"
$strFilter = "(&(objectCategory=User)(Department=*$Dept*))"
$colResults = Get-ADUser -LDAPFilter $strFilter |
Select-Object -Expand DistinguishedName
2) I am also using a script from the person before me, that performs a query in SQL combined with LanDesk. See below:
$SQLServerLANDESK = "usernam\password"
$SqlConnectionLANDESK = New-Object System.Data.SqlClient.SqlConnection
$global:dt = new-object System.Data.DataTable
$LONA = ""
$o = 0
function doit() {
$SqlConnectionLANDESK.ConnectionString = "Server=$SQLServerLANDESK; Database= $SQLDBNameLANDESK;uid=useraidi; pwd=parola"
$SqlConnectionLANDESK.Open()
$QueryLANDesk = #"
SELECT DISTINCT A0.DISPLAYNAME, A0.LOGINNAME,A0.PRIMARYOWNER,A0.TYPE, A1.OSTYPE, A2.SUITENAME, A2.PUBLISHER, A2.VERSION
FROM Computer A0 (nolock) LEFT OUTER JOIN Operating_System A1 (nolock) ON A0.Computer_Idn = A1.Computer_Idn LEFT OUTER JOIN AppSoftwareSuites A2 (nolock) ON A0.Computer_Idn = A2.Computer_Idn
WHERE A0.DEVICENAME like '%D02DI0907061%'
or A0.DEVICENAME like '%D02DI0929860%'
"#
$CommandLANDesk = new-object System.Data.SqlClient.SqlDataAdapter ($QueryLANDesk, $SqlConnectionLANDESK)
$CommandLANDesk.fill($dt) | out-null
$dtrc = $dt.Rows.Count
Write-Host "($i) Searching all cores ($dtrc machines)..."
$SqlConnectionLANDESK.Close()
}
foreach ($i in 1..10)
{if ($i -eq 6) {continue}
$SQLDBNameLANDESK = "database"
$SQLServerLANDESK = "username\parola"
doit
}
Write-Host
$dt.select("displayname like '%$LONA%'") | export-csv H:\TEST\add-remove_TEST_LOGINNAME.csv # | foreach { $o++ }
# "$o machines found."
I want to be able to basically connect the result search from point 1 and match it with the "PRIMARYOWNER" which is in DistinguishedName format and to store those in a table back in SQL. How do I do that?

Powershell - Change variables based on CSV file

I'm attempting to use a CSV file for automatic computer naming with powershell. Our Computer names carry general model numbers which tell me the actual type of computer. For instance, Model # 2537CU1 is a Lenovo T410. I want to pull the model number and change the variable to T410.
I can already get the model number with a get-wmiobject call to the computersystem class, but to create a variable with the "type" data, like T410 I'm having to use a bunch of If, and elseif statements. This is due to the sheer number or models/types we support. My goal is to have a CSV to edit instead of the script itself. I just want the corresponding Model to get the right variable for the "type" of machine.
So here's where my head is so far and I know I'm way off:
$model = Get-WmiObject -Class Win32_ComputerSystem | select -ExpandProperty Model
$modelCSV = "0"
$typeCSV = "0"
Import-Csv -Path "C:\TechDiv\ComputerModel_Type.csv" |
ForEach-Object {
$modelCSV += $_.Model
$typeCSV += $_.Type
}
if ($modelCSV -like $model)
{
$model = $typeCSV
}
Where my CSV looks like this:
Model, Type
0401B7U, A70Z
0401R6U, A70Z
1165A3U, A70Z
25521H8, 0E31
2552Ck1, 0E31
7360, 0M58
7483, 0M58
7630, 0M58
Ultimately my computer naming script will concatenate the model and serial number for easy collections and updates. Example with model/serial name (0's pad the left serial to fill 10 spaces): T410000R84D06Z
Here is how I would do it:
$model = Get-WmiObject -Class Win32_ComputerSystem | select -ExpandProperty Model
$modelCSV = "0"
$typeCSV = "0"
$modelList = Import-Csv -Path "C:\TechDiv\ComputerModel_Type.csv" |
$modelName = $modelList | Where-Object{$_.Model -eq $model}
I'd load that CSV into a hash table:
$ModelHash = #{}
Import-Csv -Path "C:\TechDiv\ComputerModel_Type.csv" |
ForEach-Object { $ModelHash[$_.Model = $_.Type }
$model = Get-WmiObject -Class Win32_ComputerSystem | select -ExpandProperty Model
$Type = $ModelHash[$Model]

Retrieve calendar items (Outlook API, WebDAV) displaying strange behaviour

We are writing an MS Outlook plugin. To satisfy our business-logic, it should check all appointments between some dates. We are experiencing several problems with retrieving all items from calendars. We tried two options:
Outlook API. We use the standard logic that is described in MSDN - sort items by [Start], set IncludeRecurrences to True and run the Find\Restrict query over calendar items like here. It works fine in our test environment. However, in our customer's environment: For recurring appointments, start and end dates are set to the corresponding dates of a 'master appointment.' For example, in some room's calendar we have a weekly appointment that was created in January, and if we try to find all items in August, we get among others four items of this recurring appointment, but their start and end dates are set to January. But Outlook displays correct dates in the same calendar...
Very bad, but we still have WebDAV! We write a simple test application and try to query all items from the calendar using WebDAV. Of course, we didn't reinvent the wheel and just pasted the code from documentation. The previous problem is solved, but the next one arises: It doesn't return recurring items that were created more than approximately six months ago. I Haven't a clue - there are no parameters restricting 'old' items!
What is wrong? Are we missing something important?
Technical details: Exchange 2003, Outlook 2003-2010. Frankly speaking, the first error disappears if we turn on Cached Exchange Mode, but we can't do that.
var nameSpace = application.GetNamespace("MAPI");
var recepient = nameSpace.CreateRecipient(roomEMail);
recepient.Resolve();
var calendar = nameSpace.GetSharedDefaultFolder(recepient, OlDefaultFolders.olFolderCalendar);
var filter = string.Format("[Start]<'{1}' AND [End]>'{0}'",
dateFrom.ToString("dd/MM/yyyy HH:mm", CultureInfo.InvariantCulture), dateTo.ToString("dd/MM/yyyy HH:mm", CultureInfo.InvariantCulture)
);
var allItems = calendar.Items;
allItems.Sort("[Start]");
allItems.IncludeRecurrences = true;
var _item = allItems.Find(filter);
while (_item != null) {
AppointmentItem item = _item as AppointmentItem;
if (item != null) {
if (item.Subject != "some const")
&& (item.ResponseStatus != OlResponseStatus.olResponseDeclined)
&& (item.MeetingStatus != OlMeetingStatus.olMeetingReceivedAndCanceled
&& item.MeetingStatus != OlMeetingStatus.olMeetingCanceled))
{
/* Here we copy item to our internal class.
* We need: Subject, Start, End, Organizer, Recipients, MeetingStatus,
* AllDayEvent, IsRecurring, RecurrentState, ResponseStatus,
* GlobalAppointmentID */
}
}
_item = allItems.FindNext();
}
UPDATE 1:
Additional research using OutlookSpy shows that the problem is not in our code - the Start\End dates are incorrect inside the API when Cached Exchange Mode is off. But Outlook developers were aware of it, and they somehow display correct dates in calendars! Does anyone know how?
UPDATE 2:
Answer from Outlook Support Escalation Engineer:
Based on this, I can confirm that this is a problem in our product.
Possible cause:
Sort after setting IncludeRecurrences.
Here is my code of a PowerShell module that retrieves Outlook items between two dates.
And a little applet to check for changes and send an email including the agenda updates, which comes handy when you don't have mobile access to the Exchange.
Path: Documents\WindowsPowerShell\Modules\Outlook\expcal.ps1
Function Get-OutlookCalendar
{
<#
.Synopsis
This function returns appointment items from default Outlook profile
.Description
This function returns appointment items from the default Outlook profile. It uses the Outlook interop assembly to use the olFolderCalendar enumeration.
It creates a custom object consisting of Subject, Start, Duration, Location
for each appointment item.
.Example
Get-OutlookCalendar |
where-object { $_.start -gt [datetime]"5/10/2011" -AND $_.start -lt `
[datetime]"5/17/2011" } | sort-object Duration
Displays subject, start, duration and location for all appointments that
occur between 5/10/11 and 5/17/11 and sorts by duration of the appointment.
The sort is the shortest appointment on top.
.Notes
NAME: Get-OutlookCalendar
AUTHOR: ed wilson, msft
LASTEDIT: 05/10/2011 08:36:42
KEYWORDS: Microsoft Outlook, Office
HSG: HSG-05-24-2011
.Link
Http://www.ScriptingGuys.com/blog
#Requires -Version 2.0
#>
echo Starting... Initialize variables
Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null
$olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
$olCalendarDetail = "Microsoft.Office.Interop.Outlook.OlCalendarDetail" -as [type]
echo ... Getting ref to Outlook and Calendar ...
$outlook = new-object -comobject outlook.application
$namespace = $outlook.GetNameSpace("MAPI")
$folder = $namespace.getDefaultFolder($olFolders::olFolderCalendar)
echo ... Calculating dates ...
$now = Get-Date -Hour 0 -Minute 00 -Second 00
echo From $a To $b
echo ... Getting appointments ...
$Appointments = $folder.Items
$Appointments.IncludeRecurrences = $true
$Appointments.Sort("[Start]")
echo ... Setting file names ...
$oldfile = "$env:USERPROFILE\outlook-calendar.bak"
echo oldfile: $oldfile
$newfile = "$env:USERPROFILE\outlook-calendar.txt"
echo newfile: $newfile
$calfile = "$env:USERPROFILE\outlook-calendar.ics"
echo calfile: $calfile
echo ... Exporting calendar to $calfile ...
$calendarSharing = $folder.GetCalendarExporter()
$calendarSharing.CalendarDetail = $olCalendarDetail::olFullDetails
$calendarSharing.IncludeWholeCalendar = $false
$calendarSharing.IncludeAttachments = $false
$calendarSharing.IncludePrivateDetails = $true
$calendarSharing.RestrictToWorkingHours = $false
$calendarSharing.StartDate = $now.AddDays(-30)
$calendarSharing.EndDate = $now.AddDays(30)
echo $calendarSharing
$calendarSharing.SaveAsICal($calfile)
echo ... Backing up $newfile into $oldfile ...
if (!(Test-Path $newfile)) {
echo "" |Out-File $newfile
}
# Backup old export into $oldfile
if (Test-Path $oldfile) {
echo "Deleting old backup file $oldfile"
del $oldfile
}
echo " ... moving $newfile into $oldfile ... "
move $newfile $oldfile
echo "... Generating text report to file $newfile ..."
$Appointments | Where-object { $_.start -gt $now -AND $_.start -lt $now.AddDays(+7) } |
Select-Object -Property Subject, Start, Duration, Location, IsRecurring, RecurrenceState |
Sort-object Start |
Out-File $newfile -Width 100
echo "... Comparing with previous export for changes ..."
$oldsize = (Get-Item $oldfile).length
$newsize = (Get-Item $newfile).length
if ($oldsize -ne $newsize ) {
echo "!!! Detected calendar change. Sending email..."
$mail = $outlook.CreateItem(0)
#2 = high importance email header
$mail.importance = 2
$mail.subject = $env:computername + “ Outlook Calendar“
$mail.Attachments.Add($newfile)
$mail.Attachments.Add($calfile)
$text = Get-Content $newfile | Out-String
$mail.body = “See attached file...“ + $text
#for multiple email, use semi-colon ; to separate
$mail.To = “your-email#your-mail-domain.com“
$mail.Send()
}
else {
echo "No changes detected in Calendar!"
}
} #end function Get-OutlookCalendar
Function Get-OutlookCalendarTest
{
echo starting...
Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null
$olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
$outlook = new-object -comobject outlook.application
$namespace = $outlook.GetNameSpace("MAPI")
$folder = $namespace.getDefaultFolder($olFolders::olFolderCalendar)
$a = Get-Date -Hour 0 -Minute 00 -Second 00
$b = (Get-Date -Hour 0 -Minute 00 -Second 00).AddDays(7)
echo From $a To $b
$Appointments = $folder.Items
$Appointments.IncludeRecurrences = $true
$Appointments.Sort("[Start]")
$Appointments | Where-object { $_.start -gt $a -AND $_.start -lt $b } | Select-Object -Property IsRecurring, RecurrenceState, Subject, Start, Location
} #end function Get-OutlookCalendarTest
This is the code to invoke the PowerShell function in the module:
Path: Documents\WindowsPowerShell\mono.ps1
Import-Module -Name Outlook\expcal.psm1 -Force
$i=0
#infinite loop for calling connect function
while(1)
{
$i = $i +1
Write-Output "Running task Get-OutlookCalendar ($i)"
Get-OutlookCalendar
start-sleep -seconds 300
}
To run the PowerShell script, use powershell.exe. To run this on startup, a shortcut on "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\":
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "C:\Users\%USERNAME%\Documents\WindowsPowerShell\mono.ps1"