Writing PowerPoint Presentations

When creating presentations in PowerPoint (or with other programs) there are a lot of things to think about. This article explains some of the discoveries that I have made when working with Microsoft Word 2007 and Microsoft PowerPoint 2007 to create course material for technical training.

This article will not cover the rhetorical aspects of teaching or writing presentations, but more focus on giving tips and tricks that can help you ease the burden of writing and maintaining presentations.

PowerPoint Templates

When I start the work with creating a presentation I usually take advantage of a template that contains the company profile with logo, header, footer, etc. A good PowerPoint template should contain slide templates for e.g. writing bulleted lists, displaying tables, images or objects, topic pages, sub topic pages, etc.

If you are not satisfied with the slide templates you  can alter the PowerPoint template in: View / Slide Master. When you have entered the Slide Master view you can add necessary slide templates, change already existing templates or delete unnecessary templates. The templates are arranged in a hierarchy with a top template with a default layout and descending templates that specializes specific parts of the master. This means that if you put the company logo on the master template you will have the logo on all descending templates if not overridden. The templates you define here will appear in the dialog for inserting a new slide when you are in normal mode.

Arranging slides

When the presentation grows and ends up in many slides it can be cumbersome to arrange the slides in a specific order. In the View tab you will find the Slide Sorter layout. This view will help you sort the slides simply by dragging and dropping slides in the desired order.

Embedded objects

A normal way of doing things is to simply make a screen dump of something you need to explain in the slideshow and lazy as we all are we simply hit ALT + PRTSC (or Print screen) and paste the data right into our presentation.

This will kill the idea of a small handy presentation.

If you consider and appreciates space and want your presentation to remain small and handy I recommend you to paste your images into Microsoft Paint or even better Adobe Photoshop so that you can reduce the size of the images. You are normally not creating a presentation that will be printed out in a size where one page can cover the walls where you are sitting with remained sharpness. In other words: Save your images as PNG files or something similar and insert them into your presentation after that. This will reduce the size of the presentation.

One side effect that occur if you don’t consider reducing your embedded images is that this will make the generated Word document even bigger (see Create a Word document based on a PowerPoint presentation).

Miscellaneous tips

Here are some tips that will easy the job with creating great presentations:

  • Create a PowerPoint template that covers all the slide types that you will need in your presentation.
  • Always try to limit the size of the images or objects you embed into the slide.
  • Use the slide sorter to arrange the slides when the presentation grows.
  • Split slides with too much content by using the shortcut CTRL + D (duplicate slide).
  • Try to make your presentation fit the rule “6+6+6”, i.e.
    • 6 Slides per subject.
    • 6 Bullets per slide.
    • 6 words per bullet.
  • Remember that images or a diagrams are better that plain text in the slides.

Lost TAB- and ESC-keys in Virtual PC

I work a lot in Virtual PC environments and one problem I have stumbled across now and then is that keys sometimes stops working. Today it happened again so I “googled” it up and found a solution to my “lost keys”-problem.

I’m currently working in a VPC running Windows 2008 Server on my laptop that runs Windows 7 (64 bit).
The problem I’ve experienced is that the ESC- and TAB-keys occasionally stops working along with the ‘ö’-key on my keyboard (Swedish).

The solution to my problem was to make changes to the Local Security Policy that is located under the Administrative Tools in Control Panel.

image

I solved my problem by doing the following changes:

  1. Close all VPC:s including the Virtual PC Console.
  2. Open the “Local Security Policy”.
  3. Select “Software Restriction Policies”.
  4. Select “Additional Rules”.
  5. Add a new “path rule” and enter the following path:

    %appdata%\microsoft\virtual pc\vpckeyboard.dll

    Note: The path to the vpckeyboard.dll-file may differ depending on what OS
    you are running. The text %appdata% is an environment variable in the system
    and can be replaced with the full path if you like.

  6. Set the “Security Level” to “Unrestricted”.
  7. Save the new rule by clicking the OK-button
  8. Start your Virtual PC and now the key should work just fine.

The image below shows the Local Security Policy with the “Software Restriction Policies”-node selected.

image

MembershipProvider – Importing old user-accounts

Recently i stumbled into a problem where I needed to import already existing user-accounts from an old website into a new EPiServer website that was using the ASP.NET Membership-system.

The question was:

  • How can you automatically create approximately 7,500 user-accounts in an easy way by using the ASP.NET Membership-system?
  • What are the requirements for doing this and is it possible to connect the newly created user-account with a certain role or several roles?
  • What about Profiles? Can you create the UserProfile as a part of the import-process?

Well, after a little research i came up with the following solution:

  • Create a Windows desktop-application (this is not a requirement, but it eases the development process) that uses the classes for the ASP.NET Mebership system.
  • Make sure the application can access the datasource for the old useraccounts; preferable a SQL Server database or some sort of datasource that is easy to extract data from.
  • Log every account that is imported even if the import failed in order to investigate the reason for the failure.

Requirements:

  • The passwords must be in clear-text in the datasource you are importing from. When the account is created in the ASP.NET Membership-system the password will be hashed/encrypted which means that you will not be able to read it in clear-text, so this is a one-way import process.

The solution is quite simple and straightforward. A couple of methods and some configuration makes it possible to create n user-accounts from the old membership-system.

Sourcecode for the client application.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Web;
using System.Web.Profile;
using System.Web.Security;
using System.IO;
 
namespace AccountConverter
{
    public partial class frmConvert : Form
    {
        public frmConvert()
        {
            InitializeComponent();
        }
 
        /// <summary>
        /// Start the conversion process.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btConvert_Click(object sender, EventArgs e)
        {
            // Get all active users 
            List<ConvertingCustomer> oldAccounts = GetOldAccounts();
 
            progressBar1.Minimum = 0;
            progressBar1.Maximum = oldAccounts.Count();
 
            // Iterate through all users that should be converted.
            int i = 0;
            foreach (ConvertingCustomer account in oldAccounts)
            {
                // Set current rownumber
                i += 1;
 
                try
                {
                    // Convert the current account
                    CreateAccount(account);
 
                    // Update the listbox with status for the current account
                    UpdateListBox(i, string.Format("SUCCESS: {0}\t{1}",
                        account.AccountNumber,
                        account.UserName));
 
                    // Log that the account was successfully converted
                    Log(account.AccountNumber, ConvertStatus.SUCCESS.ToString(), "Account was successfully converted.");
                }
                catch (Exception ex)
                {
                    // Update the listbox with status
                    UpdateListBox(i, string.Format("FAILED: {0}\t{1}\t{2}",
                        account.AccountNumber,
                        account.UserName,
                        ex.Message));
 
                    // Log that the account could not be converted
                    Log(account.AccountNumber, ConvertStatus.FAILED.ToString(), ex.Message);
                }
 
                // Update the progressbar
                progressBar1.Value = i;
                progressBar1.Update();
            }
        }
 
        /// <summary>
        /// Convert a single account. Create a new account with the ASP.NET Membership-system, connect it to a certain role and create an user-profile.
        /// </summary>
        /// <param name="convertingCustomer">An object with necessary data for the customer to be converted.</param>
        private void CreateAccount(ConvertingCustomer convertingCustomer)
        {
            // Create the User
            MembershipUser mu = Membership.CreateUser(
                string.Format("{0}{1}", "TEST_", convertingCustomer.UserName),
                convertingCustomer.Password,
                convertingCustomer.UserName);
            mu.IsApproved = true;
 
            // Add the user to the Customers-role
            Roles.AddUserToRoles(
                convertingCustomer.UserName,
                new string[] { "Customers" });
 
            // Update the User-Profile
            WebProfile webProfile = new WebProfile(WebProfile.Create(convertingCustomer.UserName));
            webProfile.Email = convertingCustomer.UserName;
            webProfile.CustomerNumber = convertingCustomer.AccountNumber;
            // ... add your own fields here
            webProfile.Save();
        }
        
        /// <summary>
        /// Connect to a datasource and get data for the old accounts that is to be converted.
        /// </summary>
        /// <returns></returns>
        private List<ConvertingCustomer> GetOldAccounts()
        {
            // Get old account-data.
            //var myOldAccounts = datasource.GetAccounts();
 
            // Get accounts from datasource
            //var accountDataList = from account in myOldAccounts
            //                      select new ConvertingCustomer
            //                      {
            //                          AccountNumber = account.CustomerId,
            //                          UserName = account.UserName,
            //                          Password = account.Password
            //                      };
            //return accountDataList.ToList<ConvertingCustomer>(); 
            
            return new List<ConvertingCustomer> 
            {
                AccountNumber = "123456",
                UserName = "TestUser@labs.com",
                Password = "Pa$$w0rd"
            }
        }
        
        /// <summary>
        /// Log the progress with the account conversion.
        /// </summary>
        /// <param name="accountNumber">The customers accountnumber in the backend.</param>
        /// <param name="status">Status of the progress.</param>
        /// <param name="message">An errormessage or a message with information about the conversion.</param>
        private void Log(string accountNumber, string status, string message)
        {
            using (StreamWriter sw = new StreamWriter(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "convert.log"), true, Encoding.UTF8))
            {
                sw.WriteLine("{0}\t{1}\t{2}", status, accountNumber, message);
            }
        }
 
        /// <summary>
        /// Update the ListBox in the GUI to display the progress.
        /// </summary>
        /// <param name="recordNumber">An integer with the rownumber of the account that was processed.</param>
        /// <param name="message">The resulting message from the conversion process.</param>
        private void UpdateListBox(int recordNumber, string message)
        {
            lbConvertProgress.Items.Insert(0, string.Format("#{0} - {1}", recordNumber, message));
            lbConvertProgress.Update();
        }
        
        /// <summary>
        /// An internal class used to handle customer-data. 
        /// </summary>
        private class ConvertingCustomer
        {
            public string UserName;
            public string Password;
            public string AccountNumber;
        }
        
        /// <summary>
        /// An enum with conversion-status.
        /// </summary>
        private enum ConvertStatus
        {
            SUCCESS,
            FAILED
        }
 
    }
}

 

The App.Config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
        <add name="MyDataSourceConnectionString"
             connectionString="Data Source=myDatabaseServer;Database=myDatabase;User Id=myDBUser;password=P@ssw0rd;Connection Timeout=30"
             providerName="System.Data.SqlClient" />
    </connectionStrings>
    <system.web>
        <membership defaultProvider="SqlServerMembershipProvider">
            <providers>
                <clear/>
                <add name="SqlServerMembershipProvider"
                     type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
                     connectionStringName="MyDataSourceConnectionString"
                     requiresQuestionAndAnswer="false"
                     applicationName="MyWebApplication"
                     requiresUniqueEmail="true"
                     passwordFormat="Hashed"
                     maxInvalidPasswordAttempts="5"
                     minRequiredPasswordLength="6"
                     minRequiredNonalphanumericCharacters="0"
                     passwordAttemptWindow="10"
                     passwordStrengthRegularExpression=""/>
            </providers>
        </membership>
        <roleManager enabled="true"
                     defaultProvider="SqlServerRoleProvider">
            <providers>
                <clear/>
                <add name="SqlServerRoleProvider"
                     connectionStringName="MyDataSourceConnectionString"
                     applicationName="MyWebApplication"
                     type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
            </providers>
        </roleManager>
        <profile enabled="true"
                 defaultProvider="SqlProfile"
                 automaticSaveEnabled="true">
            <properties>
                <add name="CustomerNumber"
                     type="System.String"/>
                <add name="Email"
                     type="System.String"/>
                <add name="Name"
                     type="System.String"/>
                <add name="Phone"
                     type="System.String"/>
                <add name="CellPhone"
                     type="System.String"/>
            </properties>
            <providers>
                <clear/>
                <add name="SqlProfile"
                     type="System.Web.Profile.SqlProfileProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
                     connectionStringName="MyDataSourceConnectionString"
                     applicationName="MyWebApplication"/>
            </providers>
        </profile>
    </system.web>
    <appSettings />
</configuration>
Technorati-taggar: ,,

First impression of SCRUM

Last week I got the opportunity to attend a course in becoming a CSM, i.e. a Certified Scrum Master. The course was given by a company named Crisp in Stockholm, and I think that Henrik Kniberg together with Jeff Sutherland did a great job in delivering what SCRUM really is and how it should be used.

So, what is SCRUM?

SCRUM is a framework that can be used to manage projects. It is not a methodology that states exactly how things should be done, but instead it is an agile way of working with development in any kind. It doesn’t have to be a project in the software industry since SCRUM is a tool for managing any kind of development.

So, how does it work?

A basic rule in SCRUM is that things that should be done must be time-boxed. What is the reason for this you might ask? Well, the time-boxing is there because if we time-box all the parts in the thing that we are building we will soon get a picture of how long time things really takes and by that we can predict when the whole will be done.

The heart of SCRUM is the Product Backlog which basically is a list of the things that we want to be able to do with our new system. Each "thing" in the Prodcut Backlog is called a story, or sometimes called a workitem, and is a specific function or feature in the thing we are building. When the Product Backlog is established and the stories are added to it, the Product Owner (I’ll get back to this person later in this posting) will soon have a complete list of whats important in the new system that is to be build, and this is really important. 

Roles in SCRUM

There are a number of important roles in SCRUM and each role is responsible for doing their thing.

  • The Product Owner is responsible for the Product Backlog, and this role is usually the customer or managers.
  • The SCRUM-team is a group of 5-7 persons ± 2 persons. The groups purpose is to take care of the stories that are to be done in each Sprint in the project.
  • The SCRUM-master is responsible for removing impediments, i.e. taking care of problems that hinders the SCRUM-team for doing their work.

Matching passwords – a look into the wonderful world of regular expressions

Ok, so I am a big fan of regular expressions. There lies a great strength in those few characters that you write to validate your input, but it can be a little bit tricky to get the expressions to do exactly what you want.

I got a question from a collegue that wanted to validate passwords with regex, but a task that seemed so trivial was as always not that easy. His original regex was ^.*(?=.{9,})(?=.*[0-9]{2,})(?=.*[a-z\d]).*$ but the problem was that it only validated strings that had two numbers in a row and not two numbers that could appear on different locations in the string. 

I began to dive into the problem based on the rules that was stated for the passwords that should be validated:

  • Minimum 8 charachters in the password.
  • Minimum 2 numbers somewhere in the password.

Lets analyze the regular expression above:

  • The characters in the beginning ^.* tells that zero or infinite alpha- or non alphanumeric characters may occur from the beginning of the string to the start of the pattern that matches the regular expression. The caret sign, i.e. ^ tells that the matching should start from the beginning of the text. The dot, i.e. . means any alpha- or non-alphanumeric character. The asterisk, i.e. * means that the preceding character should occur zero or infinite times.
    • This part of the regular expression was not ok since it had no purpose.
  • The construct (?=.{8,}) tells that the string should contain 9 characters that can be alphanumeric or non alphanumeric characters.  
    • So this construct was almost ok since it did what it was intended to do.
  • The construct (?=.*[0-8]{2,}) states that we are looking for zero or infinite alpha- or non alphanumeric characters followed by two numbers.
    • This part of the expression had no purpose.
  • The construct (?=.*[a-z\d]) tells almost the same as above, but are only looking for alphabetical charachters between a-z or any number.
    • This part of the regular expression had no purpose.
  • The last part of the regular expression .*$ states that we are looking for zero or infinite alpha- or non alphanumeric characters that occurs before the end of the string to be searched. The dollar sign, i.e. $ marks that it is the end of the string.
    • This part of the regular expression was not ok since it had no purpose.

So how do we find the solution to this problem? Well, if we take the construct (?=.{8,}) we have a good start to begin with. The problem was that the string should contain 8 charachters and of all the characters ther should be at least two numbers. Hmm, two numbers that would be (?=\d{2,}) since \d is the shorthand for any number in regex and {2,} tells that it should be two or infinite numbers in the string to be searched, but this is not enough since this pattern tells that it should be two numbers in a row. The correct pattern is (?=.*\d{2,}) which gives us the correct result.

What happens if we combine these two constructs? Well, we get almost what we want. The solution to the problem was a combination of the patterns described above which yielded in the pattern (?=.{8,})(?=(.*\d){2,})

To state that the password should containt at least one non alphanumeric character we could add the pattern (?=(.*\W){1,}) which would give us the final result of the pattern (?=.{8,})(?=(.*\d){2,})(?=(.*\W){1,})

Hope that this gives you some clues on the power and complexity of regular expressions.

How to expose data for each month in an intervall

I recently stumbled into a problem where I was supposed to display aggregated data based on a time-period for 15 months.

Ok, so this seems to be a trivial thing, right? It is just to write a query such as:

SELECT  CAST(DATEPART(year, Date) AS CHAR(4)) 
          + REPLICATE('0', 2 - LEN(CAST(DATEPART(month, Date) AS CHAR(2))))
          + CAST(DATEPART(month, Date) AS CHAR(2)) MeetingDate,
                COUNT(Date) Occasions
FROM TestData
GROUP BY CAST(DATEPART(year, Date) AS CHAR(4)) 
          + REPLICATE('0', 2 - LEN(CAST(DATEPART(month, Date) AS CHAR(2))))
          + CAST(DATEPART(month, Date) AS CHAR(2))
ORDER BY 1

This query yields in the following result in my scenario:

MeetingDate Occasions 
-------------------------------- ----------- 
200709 1 
200711 1 
200712 1 
200803 1 
200805 1 
200806 3 
200807 1 
200808 3 
200810 2 
200811 1 

(10 row(s) affected)

Well, it is not that simple because there might be gaps in the data, i.e. some months have no records, so the results above displays September, November, December, but not October, which is not our desired result since we want to expose all months between the first and the last occasion even if there is no data for the current month.

So, how do we write a query that gives us this result?

I came across the concept of an auxilliary table of numbers that I read about in Itzik Ben-Gan’s book "SQL Server – Querying". Itzik tells that by using an auxilliary table of numbers it is possible to easily get a numbered sequence that can be used to create a resultset which can be very handy if you don’t have a logical sequence.

After a little brainstorming I came up with the following solution which solved my problem.

First som preparations needed for our example

USE Master 
GO 
CREATE DATABASE Test 
GO 
USE Test 
GO 

Create the table that will contain the numbered sequence, i.e. the auxilliary table of numbers.

This table will only contain numbers from 1 to n.

CREATE TABLE dbo.Nums (Num int) 
GO

Populate the dbo.Nums table with the numbers needed. I only needed a small amount of numbers for my solution, but

if you need more numbers, just add a larger number below or Google for "Auxilliary table of numbers" and "Itzik Ben-Gan".

DECLARE @CurrentNumber int 
SET @CurrentNumber = 0 
WHILE @CurrentNumber < 50 
BEGIN 
SELECT @CurrentNumber = @CurrentNumber + 1 
INSERT INTO dbo.Nums VALUES (@CurrentNumber) 
END 
GO 

Create some test-data for this example

CREATE TABLE TestData ( 
ID int identity not null, 
Date datetime not null 
) 
GO 
INSERT INTO TestData VALUES (dateadd(month, -1, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -2, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -2, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -4, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -4, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -4, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -5, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -6, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -6, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -6, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -7, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -9, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -12, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -13, getdate())) 
INSERT INTO TestData VALUES (dateadd(month, -15, getdate())) 
GO

Get the earliest and latest date from the testdata; these dates will be used to get the number of months for the time-period.

DECLARE @MinDate datetime 
SET @MinDate = (SELECT MIN(Date) FROM testdata) 
DECLARE @MaxDate datetime 
SET @MaxDate = (SELECT MAX(Date) FROM testdata)

Get the number of months between the first and last date.

DECLARE @NumberOfMonths int 
SET @NumberOfMonths = (SELECT DATEDIFF(MONTH, @MinDate, @MaxDate))

Create a table variable to contain the intermediate result.

DECLARE @TempData TABLE ( 
DateTemp varchar(6), 
Occasions int, 
MonthNo int)

Populate the table-variable with the correct number of rows that corresponds to the @NumberOfMonths-variable.

INSERT INTO @TempData 
SELECT CAST(DATEPART(year, DATEADD(MONTH, Num -1, @MinDate)) AS CHAR(4)) 
+ REPLICATE('0', 2 - LEN(CAST(DATEPART(month, DATEADD(MONTH, Num - 1, @MinDate)) AS CHAR(2)))) 
+ CAST(DATEPART(month, DATEADD(MONTH, Num - 1, @MinDate)) AS CHAR(2)), 
0, 
Num 
FROM dbo.Nums 
WHERE Num <= @NumberOfMonths

Update the table-variable with correct data.

UPDATE @TempData 
SET 
Occasions = Occasions + ar.NumberOfOccasions 
FROM ( 
SELECT 
COUNT(Date) NumberOfOccasions, 
DATEDIFF(month, @MinDate, Date) + 1 MonthName 
FROM TestData 
GROUP BY DATEDIFF(month, @MinDate, Date) + 1 
) ar 
WHERE MonthNo = ar.MonthName

Display the correct result from the table variable.

SELECT * FROM @TempData 
ORDER BY MonthNo

Here is the desired result that we wanted

DateTemp Occasions MonthNo 
-------- ----------- ----------- 
200709 1 1 
200710 0 2 
200711 1 3 
200712 1 4 
200801 0 5 
200802 0 6 
200803 1 7 
200804 0 8 
200805 1 9 
200806 3 10 
200807 1 11 
200808 3 12 
200809 0 13 
200810 2 14

(14 row(s) affected)

Clean up and delete the demo database.

USE Master 
GO 
DROP DATABASE Test 
GO

Hello world!

So, finally I got myself a technical blog where I can write down my thoughts and discoveries in the magic world of development. I have been thinking about getting this blog for a really long time now, but I havent really had the time (or havent been smart enough) to do it until now.

I hope you will find some of my posts useful and I’m looking forward to hear from you.

Cheers!
   Dan