diff --git a/ihaskell.cabal b/ihaskell.cabal
--- a/ihaskell.cabal
+++ b/ihaskell.cabal
@@ -7,7 +7,7 @@
 -- PVP summary:      +-+------- breaking API changes
 --                   | | +----- non-breaking API additions
 --                   | | | +--- code changes with no API change
-version:             0.3.0.0
+version:             0.3.0.1
 
 -- A short (one-line) description of the package.
 synopsis:            A Haskell backend kernel for the IPython project.
@@ -43,14 +43,18 @@
 cabal-version:       >=1.16
 
 data-files: 
-  profile/profile.tar
+    installation/ipython.sh
+    installation/update.sh
+    installation/virtualenv.sh
+    installation/run.sh
+    profile/profile.tar
 
 library
   hs-source-dirs:      src
   default-language:    Haskell2010
   build-depends:       
                        base                 ==4.6.*,
-                       aeson                >=0.6,
+                       aeson                >=0.6.1,
                        base64-bytestring    >=1.0,
                        bytestring           >=0.10,
                        cereal               ==0.3.*,
@@ -192,6 +196,7 @@
                        parsec               -any,
                        process              >=1.1,
                        random               >=1.0,
+                       setenv               -any,
                        shelly               >=1.3,
                        split                >= 0.2,
                        strict               >=0.3,
@@ -202,7 +207,8 @@
                        unix                 >= 2.6,
                        utf8-string          -any
 
-  extensions: DoAndIfThenElse
+  default-extensions:
+              DoAndIfThenElse
               OverloadedStrings
               ExtendedDefaultRules
 
diff --git a/installation/ipython.sh b/installation/ipython.sh
new file mode 100644
--- /dev/null
+++ b/installation/ipython.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+set -e
+
+# Which virtualenv to use.
+VIRTUALENV=$1
+
+# Commit hash to install.
+COMMIT=$2
+
+# Activate the virtualenv.
+source $VIRTUALENV/bin/activate
+
+# Install all necessary dependencies with Pip.
+echo "Installing dependency (pyzmq)."
+pip install pyzmq==14.0.1
+
+echo "Installing dependency (markupsafe)."
+pip install markupsafe==0.18
+
+echo "Installing dependency (jinja2)."
+pip install jinja2==2.7.1
+
+echo "Installing dependency (tornado)."
+pip install tornado==3.1.1
+
+echo "Installing dependency (pygments)."
+pip install pygments==1.6
+
+# Install IPython itself.
+echo "Installing IPython (this may take a while)."
+pip install -e git+https://github.com/ipython/ipython.git@$COMMIT#egg=ipython-dev
diff --git a/installation/run.sh b/installation/run.sh
new file mode 100644
--- /dev/null
+++ b/installation/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -e
+
+# Which virtualenv to use.
+VIRTUALENV=$1
+shift
+
+# Activate the virtualenv.
+source $VIRTUALENV/bin/activate
+
+# Run IPython.
+ipython $@
diff --git a/installation/update.sh b/installation/update.sh
new file mode 100644
--- /dev/null
+++ b/installation/update.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+set -e
+
+# Which virtualenv to use.
+VIRTUALENV=$1
+
+# Commit hash to install.
+COMMIT=$2
+
+# Find out current IPython commit hash.
+cd $VIRTUALENV/src/ipython
+CURRENT_COMMIT=`git rev-parse HEAD`
+if [ $CURRENT_COMMIT != $COMMIT ]; then
+  # Activate the virtualenv.
+  source $VIRTUALENV/bin/activate
+
+  # Update IPython.
+  echo "Updating IPython (this may take a while)."
+  pip install --upgrade -e git+https://github.com/ipython/ipython.git@$COMMIT#egg=ipython-dev
+fi;
diff --git a/installation/virtualenv.sh b/installation/virtualenv.sh
new file mode 100644
--- /dev/null
+++ b/installation/virtualenv.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+set -e
+
+# Which version of virtualenv to use.
+VIRTUALENV=virtualenv-1.9
+
+# Where to install the virtualenv.
+DESTINATION=$1
+
+# Download virtualenv.
+echo "Downloading virtualenv."
+curl -O https://pypi.python.org/packages/source/v/virtualenv/$VIRTUALENV.tar.gz
+tar xvfz $VIRTUALENV.tar.gz
+cd $VIRTUALENV
+
+# Create a virtualenv.
+echo "Creating a virtualenv."
+python virtualenv.py $DESTINATION
diff --git a/src/Hspec.hs b/src/Hspec.hs
--- a/src/Hspec.hs
+++ b/src/Hspec.hs
@@ -413,7 +413,7 @@
 
   it "parses :set x" $
     parses ":set x" `like` [
-      Directive SetOpt "x"
+      Directive SetDynFlag "x"
     ]
 
   it "parses :extension x" $
diff --git a/src/IHaskell/Eval/Evaluate.hs b/src/IHaskell/Eval/Evaluate.hs
--- a/src/IHaskell/Eval/Evaluate.hs
+++ b/src/IHaskell/Eval/Evaluate.hs
@@ -150,6 +150,9 @@
 initializeImports = do
   -- Load packages that start with ihaskell-*, aren't just IHaskell,
   -- and depend directly on the right version of the ihaskell library
+  --
+  -- XXX this will try to load broken packages, provided they depend
+  -- on the right ihaskell version
   dflags <- getSessionDynFlags
   displayPackages <- liftIO $ do
     (dflags, _) <- initPackages dflags
diff --git a/src/IHaskell/Flags.hs b/src/IHaskell/Flags.hs
--- a/src/IHaskell/Flags.hs
+++ b/src/IHaskell/Flags.hs
@@ -16,11 +16,13 @@
 -- Command line arguments to IHaskell.  A set of aruments is annotated with
 -- the mode being invoked.
 data Args = Args IHaskellMode [Argument]
+          deriving Show
 
 data Argument
   = ServeFrom String    -- ^ Which directory to serve notebooks from.
   | Extension String    -- ^ An extension to load at startup.
   | ConfFile String     -- ^ A file with commands to load at startup.
+  | IPythonFrom String  -- ^ Which executable to use for IPython.
   | Help                -- ^ Display help text.
   deriving (Eq, Show)
 
@@ -30,7 +32,6 @@
   = ShowHelp String
   | Notebook
   | Console
-  | UpdateIPython
   | Kernel (Maybe String)
   | View (Maybe ViewFormat) (Maybe String)
   deriving (Eq, Show)
@@ -48,8 +49,11 @@
     chooseMode Console = console
     chooseMode Notebook = notebook
     chooseMode (Kernel _) = kernel
-    chooseMode UpdateIPython = update
 
+ipythonFlag :: Flag Args
+ipythonFlag = 
+  flagReq ["ipython", "i"] (store IPythonFrom) "<path>" "Executable for IPython."
+
 universalFlags :: [Flag Args]
 universalFlags = [
   flagReq ["extension","e", "X"] (store Extension) "<ghc-extension>" "Extension to enable at start.",
@@ -65,21 +69,21 @@
 notebook :: Mode Args
 notebook = mode "notebook" (Args Notebook []) "Browser-based notebook interface." noArgs $
   flagReq ["serve","s"] (store ServeFrom) "<dir>" "Directory to serve notebooks from.":
+  ipythonFlag:
   universalFlags
 
 console :: Mode Args
-console = mode "console" (Args Console []) "Console-based interactive repl." noArgs universalFlags
+console = mode "console" (Args Console []) "Console-based interactive repl." noArgs $ ipythonFlag:universalFlags
 
 kernel = mode "kernel" (Args (Kernel Nothing) []) "Invoke the IHaskell kernel." kernelArg []
   where
     kernelArg = flagArg update "<json-kernel-file>"
     update filename (Args _ flags) = Right $ Args (Kernel $ Just filename) flags
 
-update :: Mode Args
-update = mode "update" (Args UpdateIPython []) "Update IPython frontends." noArgs []
-
 view :: Mode Args
-view = (modeEmpty $ Args (View Nothing Nothing) []) {
+view =
+  let empty = mode "view" (Args (View Nothing Nothing) []) "View IHaskell notebook." noArgs [ipythonFlag] in
+    empty {
       modeNames = ["view"],
       modeCheck  =
         \a@(Args (View fmt file) _) ->
@@ -93,7 +97,8 @@
           ["pdf", "html", "ipynb", "markdown", "latex"]),
         "."
       ],
-      modeArgs = ([formatArg, filenameArg], Nothing)
+      modeArgs = ([formatArg, filenameArg] ++ fst (modeArgs empty),
+                  snd $ modeArgs empty)
                                                     
   }
   where
@@ -105,14 +110,13 @@
         Nothing -> Left $ "Invalid format '" ++ fmtStr ++ "'."
     updateFile name (Args (View f _) flags) = Right $ Args (View f (Just name)) flags
   
-
 ihaskellArgs :: Mode Args
 ihaskellArgs =
   let descr = "Haskell for Interactive Computing." 
       helpStr = showText (Wrap 100) $ helpText [] HelpFormatAll ihaskellArgs
       onlyHelp = [flagHelpSimple (add Help)]
       noMode = mode "IHaskell" (Args (ShowHelp helpStr) []) descr noArgs onlyHelp in
-    noMode { modeGroupModes = toGroup [console, notebook, view, update, kernel] }
+    noMode { modeGroupModes = toGroup [console, notebook, view, kernel] }
   where 
     add flag (Args mode flags) = Args mode $ flag : flags
 
diff --git a/src/IHaskell/IPython.hs b/src/IHaskell/IPython.hs
--- a/src/IHaskell/IPython.hs
+++ b/src/IHaskell/IPython.hs
@@ -1,11 +1,8 @@
 {-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}
 {-# LANGUAGE DoAndIfThenElse #-}
--- | Description : Shell scripting wrapper using @Shelly@ for the @notebook@, @setup@, and
+-- | Description : Shell scripting wrapper using @Shelly@ for the @notebook@, and
 --                 @console@ commands.
 module IHaskell.IPython (
-  ipythonInstalled,
-  installIPython,
-  updateIPython,
   setupIPython,
   runConsole,
   runNotebook,
@@ -14,18 +11,21 @@
   getIHaskellDir,
   getSandboxPackageConf,
   nbconvert,
+  subHome,
   ViewFormat(..),
+  WhichIPython(..),
 ) where
 
-import ClassyPrelude
-import Prelude (read, reads, init)
-import Shelly hiding (find, trace, path, (</>))
-import System.Argv0
-import System.Directory
-import qualified Filesystem.Path.CurrentOS as FS
-import Data.List.Utils (split)
-import Data.String.Utils (rstrip, endswith)
-import Text.Printf
+import            ClassyPrelude
+import            Control.Concurrent (threadDelay)
+import            Prelude (read, reads, init)
+import            Shelly hiding (find, trace, path, (</>))
+import            System.Argv0
+import            System.Directory
+import qualified  Filesystem.Path.CurrentOS as FS
+import            Data.List.Utils (split)
+import            Data.String.Utils (rstrip, endswith, strip, replace)
+import            Text.Printf
 
 import qualified System.IO.Strict as StrictIO
 import qualified Paths_ihaskell as Paths
@@ -33,6 +33,11 @@
 
 import IHaskell.Types
 
+-- | Which IPython to use.
+data WhichIPython
+     = DefaultIPython           -- ^ Use the one that IHaskell tries to install.
+     | ExplicitIPython String   -- ^ Use the command-line flag provided one.
+
 -- | Which commit of IPython we are on.
 ipythonCommit :: Text
 ipythonCommit = "9c922f54af799704f4000aeee94ec7c74cada194"
@@ -42,12 +47,15 @@
 ipythonProfile = "haskell"
 
 -- | Run IPython with any arguments.
-ipython :: Bool         -- ^ Whether to suppress output.
+ipython :: WhichIPython -- ^ Which IPython to use (user-provided or IHaskell-installed).
+        -> Bool         -- ^ Whether to suppress output.
         -> [Text]       -- ^ IPython command line arguments.
         -> Sh String    -- ^ IPython output.
-ipython suppress args = do
-  ipythonPath <- ipythonExePath
-  runHandles ipythonPath args handles doNothing
+ipython which suppress args = do
+  runCmd <- liftIO $ Paths.getDataFileName "installation/run.sh"
+  venv <- fpToText <$> ipythonDir
+  let cmdArgs = [pack runCmd, venv] ++ args
+  runHandles "bash" cmdArgs handles doNothing
   where handles = [InHandle Inherit, outHandle suppress, errorHandle suppress]
         outHandle True = OutHandle CreatePipe
         outHandle False = OutHandle Inherit
@@ -79,8 +87,11 @@
 ipythonDir :: Sh FilePath
 ipythonDir = ensure $ (</> "ipython") <$> ihaskellDir
 
-ipythonExePath :: Sh FilePath
-ipythonExePath = (</> ("bin" </> "ipython")) <$> ipythonDir
+ipythonExePath :: WhichIPython -> Sh FilePath
+ipythonExePath which = 
+  case which of
+    DefaultIPython -> (</> ("bin" </> "ipython")) <$> ipythonDir
+    ExplicitIPython path -> return $ fromString path
 
 notebookDir :: Sh FilePath
 notebookDir = ensure $ (</> "notebooks") <$> ihaskellDir
@@ -102,8 +113,8 @@
 -- | Find a notebook and then convert it into the provided format.
 -- Notebooks are searched in the current directory as well as the IHaskell
 -- notebook directory (in that order).
-nbconvert :: ViewFormat -> String -> IO ()
-nbconvert fmt name = void . shellyNoDir $ do
+nbconvert :: WhichIPython -> ViewFormat -> String -> IO ()
+nbconvert which fmt name = void . shellyNoDir $ do
   curdir <- pwd
   nbdir <- notebookDir
   -- Find which of the options is available. 
@@ -125,124 +136,32 @@
             Pdf ->  ["--to=latex", "--post=pdf"]
             Html -> ["--to=html", "--template=ihaskell"]
             fmt ->  ["--to=" ++ show fmt] in
-      void $ runIHaskell ipythonProfile "nbconvert" $ viewArgs ++ [fpToString notebook]
+      void $ runIHaskell which ipythonProfile "nbconvert" $ viewArgs ++ [fpToString notebook]
 
 -- | Set up IPython properly.
-setupIPython :: IO ()
-setupIPython = do
+setupIPython :: WhichIPython -> IO ()
+
+setupIPython (ExplicitIPython path) = do
+  exists <- shellyNoDir $
+    test_f $ fromString path
+
+  unless exists $
+    fail $ "Cannot find IPython at " ++ path
+
+setupIPython DefaultIPython = do
   installed <- ipythonInstalled
   if installed
   then updateIPython
   else installIPython
-  
-  
 
--- | Update the IPython source tree and rebuild.
-updateIPython :: IO ()
-updateIPython = void . shellyNoDir $ do
-  srcDir <- ipythonSourceDir
-  cd srcDir
-  gitPath <- path "git"
-  currentCommitHash <- silently $ pack <$> rstrip <$> unpack <$> run gitPath ["rev-parse", "HEAD"]
-  when (currentCommitHash /= ipythonCommit) $ do
-    putStrLn "Incorrect IPython repository commit hash."
-    putStrLn $ "Found hash:  " ++ currentCommitHash 
-    putStrLn $ "Wanted hash: " ++ ipythonCommit 
-    putStrLn "Updating..."
-    run_ gitPath ["pull", "origin", "master"]
-    run_ gitPath ["checkout", ipythonCommit]
-    installPipDependencies
-    buildIPython
-
--- | Install IPython from source.
-installIPython :: IO ()
-installIPython = void . shellyNoDir $ do
-  installPipDependencies
-
-  -- Get the IPython source.
-  gitPath <- path "git"
-  putStrLn "Downloading IPython... (this may take a while)"
-  ipythonSrcDir <- ipythonSourceDir
-  run_ gitPath ["clone", "--recursive", "https://github.com/ipython/ipython.git", fpToText ipythonSrcDir]
-  cd ipythonSrcDir
-  run_ gitPath ["checkout", ipythonCommit]
-
-  buildIPython
-
--- | Install all Python dependencies.
-installPipDependencies :: Sh ()
-installPipDependencies = withTmpDir $ \tmpDir -> 
-    mapM_ (installDependency tmpDir) 
-      [
-        ("pyzmq", "14.0.1")
-      , ("tornado","3.1.1")
-      , ("jinja2","2.7.1")
-      -- The following cannot go first in the dependency list, because
-      -- their setup.py are broken and require the directory to exist
-      -- already.
-      , ("MarkupSafe", "0.18")
-      --, ("setuptools", "2.0.2")
-      ]
-  where
-    installDependency :: FilePath -> (Text, Text) -> Sh ()
-    installDependency tmpDir (dep, version) = sub $ do
-      let versioned = dep ++ "-" ++ version
-      putStrLn $ "Installing dependency: " ++ versioned
-
-      pipPath <- path "pip"
-      tarPath <- path "tar"
-      pythonPath <- path "python"
-
-      -- Download the package.
-      let downloadOpt = "--download=" ++ fpToText tmpDir
-      run_ pipPath ["install", downloadOpt, dep ++ "==" ++ version]
-
-      -- Extract it.
-      cd tmpDir
-      run_ tarPath ["-xzf", versioned ++ ".tar.gz"]
-
-      -- Install it.
-      cd $ fromText versioned
-      dir <- fpToText <$> ipythonDir
-      setenv "PYTHONPATH" $ dir ++ "/lib/python2.7/site-packages/"
-      let prefixOpt =  "--prefix=" ++ dir
-      run_ pythonPath ["setup.py", "install", prefixOpt]
+-- | Replace "~" with $HOME if $HOME is defined.
+-- Otherwise, do nothing.
+subHome :: String -> IO String
+subHome path = shellyNoDir $ do
+  home <- unpack <$> fromMaybe "~" <$> get_env "HOME"
+  return $ replace "~" home path
 
 
--- | Once things are checked out into the IPython source directory, build it and install it.
-buildIPython :: Sh ()
-buildIPython = do
-  -- Install IPython locally.
-  pythonPath <- path "python"
-  prefixOpt <- ("--prefix=" ++) <$> fpToText <$> ipythonDir
-  putStrLn "Installing IPython."
-  run_ pythonPath ["setup.py", "install", prefixOpt]
-
-  -- Patch the IPython executable so that it doesn't use system IPython.
-  -- Using PYTHONPATH is not enough due to bugs in how `easy_install` sets
-  -- things up, at least on Mac OS X.
-  ipyDir <- ipythonDir
-  let patchLines =
-        [ "#!/usr/bin/env python"
-        , "import sys"
-        , "sys.path = [\"" ++ fpToText ipyDir ++
-         "/lib/python2.7/site-packages\"] + sys.path"]
-  ipythonPath <- ipythonExePath
-  contents <- readFile ipythonPath
-  writeFile ipythonPath $ unlines patchLines ++ "\n" ++ contents
-
-  -- Remove the old IPython profile so that we write a new one in its
-  -- place. Users are not expected to fiddle with the profile, so we give
-  -- no warning whatsoever. This may be changed eventually.
-  removeIPythonProfile ipythonProfile
-      
-
--- | Check whether IPython is properly installed.
-ipythonInstalled :: IO Bool
-ipythonInstalled = shellyNoDir $ do
-  ipythonPath <- ipythonExePath
-  test_f ipythonPath
-
 -- | Get the path to an executable. If it doensn't exist, fail with an
 -- error message complaining about it.
 path :: Text -> Sh FilePath
@@ -254,13 +173,6 @@
       fail $ "`" ++ unpack exe ++ "` not on $PATH."
     Just exePath -> return exePath
 
--- | Use the `ipython --version` command to figure out the version.
--- Return a tuple with (major, minor, patch).
-ipythonVersion :: IO (Int, Int, Int)
-ipythonVersion = shellyNoDir $ do
-  [major, minor, patch] <- parseVersion <$>  ipython True ["--version"]
-  return (major, minor, patch)
-
 -- | Parse an IPython version string into a list of integers.
 parseVersion :: String -> [Int]
 parseVersion versionStr = map read' $ split "." versionStr
@@ -269,13 +181,14 @@
                         _ -> error $ "cannot parse version: "++ versionStr
 
 -- | Run an IHaskell application using the given profile.
-runIHaskell :: String   -- ^ IHaskell profile name. 
+runIHaskell :: WhichIPython
+           -> String   -- ^ IHaskell profile name. 
            -> String    -- ^ IPython app name.
            -> [String]  -- ^ Arguments to IPython.
            -> Sh ()
-runIHaskell profile app args = void $ do
+runIHaskell which profile app args = void $ do
   -- Try to locate the profile. Do not die if it doesn't exist.
-  errExit False $ ipython True ["locate", "profile", pack profile]
+  errExit False $ ipython which True ["locate", "profile", pack profile]
 
   -- If the profile doesn't exist, create it.
   -- We have an ugly hack that removes the profile whenever the IPython
@@ -283,25 +196,25 @@
   exitCode <- lastExitCode
   when (exitCode /= 0) $ liftIO $ do
     putStrLn "Creating IPython profile."
-    setupIPythonProfile profile
+    setupIPythonProfile which profile
 
   -- Run the IHaskell command.
-  ipython False $ map pack $ [app, "--profile", profile] ++ args
+  ipython which False $ map pack $ [app, "--profile", profile] ++ args
 
-runConsole :: InitInfo -> IO ()
-runConsole initInfo = void . shellyNoDir $ do
+runConsole :: WhichIPython -> InitInfo -> IO ()
+runConsole which initInfo = void . shellyNoDir $ do
   writeInitInfo initInfo
-  runIHaskell ipythonProfile "console" []
+  runIHaskell which ipythonProfile "console" []
 
-runNotebook :: InitInfo -> Maybe String -> IO ()
-runNotebook initInfo maybeServeDir = void . shellyNoDir $ do
+runNotebook :: WhichIPython -> InitInfo -> Maybe String -> IO ()
+runNotebook which initInfo maybeServeDir = void . shellyNoDir $ do
   notebookDirStr <- fpToString <$> notebookDir
   let args = case maybeServeDir of 
                Nothing -> ["--notebook-dir", unpack notebookDirStr]
                Just dir -> ["--notebook-dir", dir]
 
   writeInitInfo initInfo
-  runIHaskell ipythonProfile "notebook" args
+  runIHaskell which ipythonProfile "notebook" args
 
 writeInitInfo :: InitInfo -> Sh ()
 writeInitInfo info = do
@@ -314,28 +227,29 @@
   read <$> liftIO (readFile filename)
 
 -- | Create the IPython profile.
-setupIPythonProfile :: String -- ^ IHaskell profile name.
+setupIPythonProfile :: WhichIPython
+                    -> String -- ^ IHaskell profile name.
                     -> IO ()
-setupIPythonProfile profile = shellyNoDir $ do
+setupIPythonProfile which profile = shellyNoDir $ do
   -- Create the IPython profile.
-  void $ ipython True ["profile", "create", pack profile]
+  void $ ipython which True ["profile", "create", pack profile]
 
   -- Find the IPython profile directory. Make sure to get rid of trailing
   -- newlines from the output of the `ipython locate` call.
-  ipythonDir <- pack <$> rstrip <$> ipython True ["locate"]
+  ipythonDir <- pack <$> rstrip <$> ipython which True ["locate"]
   let profileDir = ipythonDir ++ "/profile_" ++ pack profile ++ "/"
 
   liftIO $ copyProfile profileDir
   insertIHaskellPath profileDir
 
-removeIPythonProfile :: String -> Sh ()
-removeIPythonProfile profile = do
+removeIPythonProfile :: WhichIPython -> String -> Sh ()
+removeIPythonProfile which profile = do
   -- Try to locate the profile. Do not die if it doesn't exist.
-  errExit False $ ipython True ["locate", "profile", pack profile]
+  errExit False $ ipython which True ["locate", "profile", pack profile]
 
   -- If the profile exists, delete it.
   exitCode <- lastExitCode
-  dir <- pack <$> rstrip <$> ipython True ["locate"]
+  dir <- pack <$> rstrip <$> ipython which True ["locate"]
   when (exitCode == 0 && dir /= "") $ do
     putStrLn "Updating IPython profile."
     let profileDir = dir ++ "/profile_" ++ pack profile ++ "/"
@@ -345,16 +259,6 @@
 copyProfile :: Text -> IO ()
 copyProfile profileDir = do
   profileTar <- Paths.getDataFileName "profile/profile.tar"
-  {-
-  -- Load profile from Resources directory of Mac *.app.
-  ihaskellPath <- shellyNoDir getIHaskellPath
-  profileTar <- if "IHaskell.app/Contents/MacOS" `isInfixOf` ihaskellPath
-               then
-                let pieces = split "/" ihaskellPath
-                    pathPieces = init pieces ++ ["..", "Resources", "profile.tar"] in
-                  return $ intercalate "/" pathPieces
-               else Paths.getDataFileName "profile/profile.tar"
-  -}
   putStrLn $ pack $ "Loading profile from " ++ profileTar 
   Tar.extract (unpack profileDir) profileTar 
 
@@ -409,3 +313,40 @@
       [] -> return Nothing
       dir:_ -> 
         return $ Just dir
+
+-- | Check whether IPython is properly installed.
+ipythonInstalled :: IO Bool
+ipythonInstalled = shellyNoDir $ do
+  ipythonPath <- ipythonExePath DefaultIPython
+  test_f ipythonPath
+
+-- | Update the IPython source tree and rebuild.
+updateIPython :: IO ()
+updateIPython = do
+  updateScript <- Paths.getDataFileName "installation/update.sh"
+  venv <- fpToText <$> shellyNoDir ipythonDir
+  runTmp updateScript [venv, ipythonCommit]
+
+  -- Remove the old IPython profile.
+  -- A new one will be regenerated when it is needed.
+  -- shellyNoDir $ removeIPythonProfile DefaultIPython ipythonProfile
+
+-- | Install IPython from source.
+installIPython :: IO ()
+installIPython = do
+  -- Print a message and wait a little.
+  putStrLn "Installing IPython for IHaskell. This may take a while."
+  threadDelay $ 3 * 1000 * 100
+
+  -- Set up the virtualenv.
+  virtualenvScript <- Paths.getDataFileName "installation/virtualenv.sh"
+  venvDir <- fpToText <$> shellyNoDir ipythonDir
+  runTmp virtualenvScript [venvDir]
+
+  -- Set up Python depenencies.
+  installScript <- Paths.getDataFileName "installation/ipython.sh"
+  runTmp installScript [venvDir, ipythonCommit]
+
+runTmp script args = shellyNoDir $ withTmpDir $ \tmp -> do
+  cd tmp
+  run_ "bash" $ pack script: args
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -43,31 +43,31 @@
     Right args ->
       ihaskell args
 
+chooseIPython [] = return DefaultIPython
+chooseIPython (IPythonFrom path:_) =
+  ExplicitIPython <$> subHome path
+chooseIPython (_:xs) = chooseIPython xs
+
 ihaskell :: Args -> IO ()
 -- If no mode is specified, print help text.
 ihaskell (Args (ShowHelp help) _) = 
   putStrLn $ pack help
-
--- Update IPython: remove then reinstall.
--- This is in case cabal updates IHaskell but the corresponding IPython
--- isn't updated. This is hard to detect since versions of IPython might
--- not change!
-ihaskell (Args UpdateIPython _) = do
-  setupIPython
-  putStrLn "IPython updated."
     
 ihaskell (Args Console flags) = showingHelp Console flags $ do
-  setupIPython
+  ipython <- chooseIPython flags
+  setupIPython ipython
 
   flags <- addDefaultConfFile flags
   info <- initInfo IPythonConsole flags
-  runConsole info
+  runConsole ipython info
 
-ihaskell (Args (View (Just fmt) (Just name)) []) =
-  nbconvert fmt name
+ihaskell (Args (View (Just fmt) (Just name)) args) = do
+  ipython <- chooseIPython args
+  nbconvert ipython fmt name
 
 ihaskell (Args Notebook flags) = showingHelp Notebook flags $ do
-  setupIPython
+  ipython <- chooseIPython flags
+  setupIPython ipython
 
   let server = case mapMaybe serveDir flags of
                  [] -> Nothing
@@ -79,7 +79,7 @@
   curdir <- getCurrentDirectory
   let info = undirInfo { initDir = curdir }
 
-  runNotebook info server
+  runNotebook ipython info server
   where
     serveDir (ServeFrom dir) = Just dir
     serveDir _ = Nothing
