MacLochlainns Weblog

Michael McLaughlin's Technical Blog

Site Admin

Archive for December, 2009

The ereg() function is gone

with one comment

Alas, poor ereg() I abused you well. PHP 5.3 has deprecated ereg() and now we must move forward with preg_match(). Along with that change, somebody asked me to show how to upload images to the file system as opposed to the database. Personally, I think they should be stored in the database.

With my bias toward databases, I threw in a virtual directory mapping in a MySQL database because it doesn’t natively support an Oracle equivalent BFILE data type. You can see this older post how to use the DBA_DIRECTORIES view in Oracle to mimic this behavior.

Naturally, MySQL is the preferred database of the person asking the question. You could also implement this exactly the same in Oracle but you really don’t want to do so. Using Oracle’s virtual directories has it’s own pre-built set of security features. They provide a more robust solution.

The code is presented as follows (setup for MySQL instructions):

  1. Create and seed the DIRECTORY table in MySQL:
-- Create a directory table.
CREATE TABLE directory
( directory_id   INT PRIMARY KEY AUTO_INCREMENT
, virtual_name   VARCHAR(30)
, directory_name VARCHAR(60));
 
-- Seed the table with a virtual directory mapping.
INSERT INTO directory VALUES ( NULL,'CMS_IMAGES','C:\\Data' );
  1. Create a MySQLCredentials.inc credentails file for inclusion in the PHP program:
1
2
3
4
5
6
7
<?php
  // Connection variables.
  define('HOSTNAME',"localhost");
  define('USERNAME',"student");
  define('PASSWORD',"student");
  define('DATABASE',"sampledb");
?>
  1. Create the PHP uploading program, named MySQLFileUpload.php:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?php
  // Set database credentials.
  include_once("MySQLCredentials.inc");
 
  // Declare input variables.
  $id = (isset($_POST['id'])) ? $_POST['id'] : 1021;
 
  // Upload a file to server in a mapped physical drive location.
  if (process_uploaded_file(map_virtual_directory($id)))
    print "Successfully Uploaded the file.<br />";
 
  // Map a virtual directory to a physical directory.
  function map_virtual_directory($virtual) {
 
    // Return successful attempt to connect to the database.
    if (!$c = @mysqli_connect(HOSTNAME,USERNAME,PASSWORD,DATABASE)) {
 
      // Print user message.
      print "Sorry! The connection to the database failed. Please try again later.";
 
      // Return error message.
      print mysqli_error();
 
      // Kill the resource.
      die();
    }
    else {
 
      // Initialize a statement in the scope of the connection.
      $stmt = mysqli_stmt_init($c);
 
      // Declare a case insensitive dynamic SQL statement.
      $sql = "SELECT directory_name FROM directory WHERE virtual_name = UCASE(?)";
 
      // Prepare the statement.
      if (mysqli_stmt_prepare($stmt,$sql)) {
 
        // Bind the input parameter to the prepared statement.
        mysqli_stmt_bind_param($stmt,'s',$virtual);
 
        // Execute the prepared statement.
        if (mysqli_stmt_execute($stmt)) {
 
          // Bind the result to a local variable.
          mysqli_stmt_bind_result($stmt,$directory);
 
          // FetchPrepare statement and link it to a connection.
          while (mysqli_stmt_fetch($stmt))
            return $directory;
        }
        else
          // Return error message.
          print mysqli_error();
      }
      else
        // Return error message.
        print mysqli_error();
 
          // Disconnect from database.
      mysqli_close($c);
    }
  }
 
  // Manage file upload.
  function process_uploaded_file($directory) {
 
    /* Assume the application may allow a virtual directory with a trailing backslash or forward
       slash to be stored in the database, and manage both scenarios across Windows and Linux. */
    if (preg_match(".Win32.",$_SERVER["SERVER_SOFTWARE"]))
      if (preg_match("/\b\\\/",$directory));
      else if (preg_match("/\b\//",$directory)) {
        $directory = substr($directory,0,strlen($directory)-1);
        $directory = $directory."\\";
      }
      else $directory = $directory."\\";
    else
      if (preg_match("/\b\//",$directory))
        $directory = substr($directory,0,strlen($directory)-1);
      else
        $directory = $directory."/";  
 
    // Check for, move uploaded file, and confirm processing.
    if (is_uploaded_file($_FILES['userfile']['tmp_name'])) {
 
      // Move temporary cache into a file directory with the uploaded file name.
      move_uploaded_file($_FILES['userfile']['tmp_name'],$directory.$_FILES['userfile']['name']);
 
      // Remove this from real code, it's here for example only. ;-)
      print "Uploaded [".$_FILES['userfile']['name']."] to".$directory."<br />";
 
      // Return true to encapsulate the functional logic on success.
	    return true;
    }
    else
      // Return false to encapsulate the functional logic on failure.
	    return false;
  }
?>
  1. Create a web page to test it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<html>
<head>
<title>
  UploadFileFormMySQL.htm
</title>
</head>
<body>
<form id="uploadForm"
      action="MySQLFileUpload.php"
      enctype="multipart/form-data"
      method="post">
  <table border=0 cellpadding=0 cellspacing=0>
    <tr>
      <td width=125>Item Number</td>
      <td>
        <input id="id" name="id" type="text">
      </td>
    </tr>
    <tr>
      <td width=125>Select File</td>
      <td>
        <input id="uploadfilename" name="userfile" type="file">
      </td>
    </tr>
    <tr>
      <td width=125>Click Button to</td>
      <td><input type="submit" value="Upload File"></td>
    </tr>
  </table>
</form>
</body>
</html>

Hope this helps a few folks. I imagine that the prepared statement with bound variables may help a few folks because it’s not found (at writing) on the php.net web site.

Written by maclochlainn

December 29th, 2009 at 2:03 am

Posted in LAMP,Linux,MAMP,PHP,Zend

CrossOver Plus Limits

without comments

While reconfiguring my iMac, it was interesting to note the bumpy road to implementing Code Weaver’s CrossOver Plus 8.0.1 for the whole Microsoft Office Suite. Previously, it had only been used to support Microsoft Visio 2007.

I discovered that a Shutdown and Start operation was required after installing CrossOver Plus because a Restart hadn’t worked. Attempting to create the WINE bottle with a reboot gave me the following error:

The Microsoft Office 2007 package requires CrossOver HTML engine to install and
run properly. Continuing with the install may produce unpredictable results.

I also found that patching the Office product failed. It’s possible that could be the network here, but there’s also no manual way to download *.msi files and apply them against the WINE bottle. It’s possible that a trick was missed and there is a way to do it. Downloading and right clicking on the *.msi file on the Mac OS X side allows you to look for an application to help but I couldn’t find a helper application in the CrossOver Plus folder tree. It may be there, however, where it is eluded me in the time my patience would allow (please comment if you know its location)?

Rather than use Google Quick Search Box I thought symbolic links would be helpful in the Applications folder since I actually also run Microsoft Office 2008. Turning to Chapter 6 in the product documentation didn’t solve the problem. It led me to an incorrect folder location.

They’re not here, as stated in the product documentation:

You can find them in the WINE bottle folders here, the default is for Windows XP:

Within the Bottle folder, you can find the Microsoft Office 2007 links here:

General Complaint

The CrossOver Plus installation suppresses the options dialog to choose a typical, full, or custom installation of Microsoft Office 2007. That means you get typical, which means you can’t connect to a remote database using the JDBC et cetera. This doesn’t meet my needs and means I’ll have to put another VMWare instance together to work with Microsoft Excel 2007 reasonably. Drat!

Written by maclochlainn

December 23rd, 2009 at 1:03 pm

The class, they survived …

with 3 comments

The rumor is that my database is hard, but I’ve always hoped it was fun and laid the foundation of success for my students. As I walked into class to give them their final exam, they had their jackets on, which isn’t uncommon for Rexburg, Idaho in December. However, that’s normal outside but unusual in the heated classroom. Then, they all took their jackets off to show their new t-shirts.

They thought it would be fun to post on the blog, so here it is. The shirts says:

SELECT   i.survived
FROM     michael_mclaughlin i
WHERE    class= CIT 320;

It’s unfortunate that they missed the enclosing quote marks around the string literal. 😉 It should be like this:

SELECT   i.survived
FROM     michael_mclaughlin i
WHERE    i.class= 'CIT 320';

Here’s to a great group of students who know how to read, write, and think SQL. Any openings out there for internships, please drop me a note.

Happy holidays!

Written by maclochlainn

December 16th, 2009 at 4:42 pm

VMWare nabs me again …

with 7 comments

When I run into failures on VMWare Fusion, they’re always a bit tedious. This one happened on my iMac (OS X Leopard) running VMWare 2 (both constrained to old releases by university governance policies). The VM is Microsoft Vista in an IDE partition, it hung after running too long. I had to force quit the application. On reboot the socket file was still there, and it gave the following error message when trying to start it.

VMwarefusionvirtualdevice0

Here’s the error in plain text, so search engines can find it for others.

Virtual device serial0: File "/var/folders/Sf/SfvoJITAHMq1Vp8bNI7QZU+++TM/-Tmp-//vmware-mmclaugh/thnuclnt-641/socket" exists, but no server is listening to it.
 
There are three possible causes for this:
 - The server is alive but not ready yet, and you can retry later.
 - The server is busy communicating with another client, so you cannot run this client at the same time.
 - A previous server exited abruptly, and you can remove the file and try again.
 
The device will be disconnected.

How to fix it?

Delete the file, right? Yes, but there’s a trick. Navigating through the -Tmp- directory required a Unix shell trick because the - (dash) is a switch and backquoting it with a \ (backslash) didn’t work. Jeff Yoder, told me the trick to change directory into a dash leading directory name. It was this:

cd -- -Tmp-

The -- is how most shells mark the end of options to a command. After a -- all - (dashes) are treated as ordinary characters.

Mark Olaveson reminded me that using the present working directory before the directory name also worked. It demotes the dash to an ordinary character too.

cd ./-Tmp-

When I got to the directory, there was the socket file. I deleted it and everything worked like a charm.

srwxrwxrwx  1 mmclaugh  staff     0 Dec  8 13:04 socket

Written by maclochlainn

December 8th, 2009 at 3:43 pm

sudo conferred powers

with 3 comments

Coming from Solaris Unix and Red Hat Linux to Ubuntu, Mac OS X and other distributions of Linux was interesting a couple years ago. The fact that you couldn’t su to the root account was tedious. It didn’t take too long (a couple momentes) to recall that I could assume those privileges in a shell, like:

admin_user@machine_name:~$ sudo sh
#

Naturally, this avoids entering sudo before a series of commands and makes administering any box simpler. It should work on all distributions but I’ve not checked ALL of them because they’re too numerous anymore. I know it works on the Mac OS X, Ubuntu, and now Fedora distributions.

Today, I got a kick from the message provided by Fedora 10 when you assume root permissions. It’s been over 20 years since I got that lecture on an AT&T box at First Interstate Bank. I imagine that any equivalent box to that is in a museum, while that bank was acquired by Wells Fargo in the early 1990s. The message from Fedora is just too funny to pass on making a comment. Here’s the screen shot:

SuperUserPowers

Hope it brought a smile to some faces …

Written by maclochlainn

December 6th, 2009 at 12:59 pm

Posted in Linux,Mac,Ubuntu

Lobs with mysqli in PHP 5.3

without comments

We got away with a bit of lazy PHP, at least through PHP 5.29. Especially when we worked with LOB columns. All that is at an end with PHP 5.3. You must now call the mysqli_stmt_store_results($stmt); function (line number 76 in the program snippet below from source) between the execute and bind result phrases.

The function transfers a result set from a prepared statement. You need to use it when performing a SELECT, SHOW, DESCRIBE, or EXPLAIN statement that you will call by the mysqli_stmt_fetch($stmt); function.

Without using it, you may only see part of a MEDIUMTEXT column displayed. Likewise, an image from a MEDIUMBLOB column won’t be displayed at all unless you store their results too, by using the same function call.

68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Prepare statement.
if (mysqli_stmt_prepare($stmt,$sql)) {
  mysqli_stmt_bind_param($stmt,"i",$id);
 
  // Execute it and print success or failure message.
  if (mysqli_stmt_execute($stmt)) {
 
    // Store result, which eliminates memory cutting off lob streams.
    mysqli_stmt_store_result($stmt);
 
    // Bind result to local variable.
    mysqli_stmt_bind_result($stmt, $desc);
 
    // Read result.
    mysqli_stmt_fetch($stmt);
 
    // Print result.
    print $desc;
  }
}

Also, that oldie ereg() function is deprecated for preg_match() in PHP 5.3 and removed in PHP 6. Don’t forget to make those changes if you were lazy like me before updating to PHP 5.3.

Written by maclochlainn

December 4th, 2009 at 11:59 pm

Posted in MySQL,PHP

Oracle 11g XDB Shake & Bake

with 33 comments

It’s a bit awkward when a post generates a new question, but here’s a quick explanation and example of using XDB (XML Database Server) outside of the realm of APEX. More or less, XDB is an Apache Server equivalent configured inside the database. It’s really a protocol server tied into the Shared Server Oracle*Net Architecture (a correction provided by Marco Gralike). As a note, testing was done by using a NAT static IP addressing for the virtual Windows XP, Vista, and 7 environments.

This blog post will show you how to experiment with the PL/SQL Web Toolkit and build both password protected and unprotected database content. It assumes you have access to the SYS privileged account.

Setting Up a Secure DAD

There’s secure and then there’s secure. This falls in the less than secure category but it does provide a password and uses basic HTTP authentication. The USER is the schema name, and the PASSWORD is the same as that for the SQL*Plus access to the schema.

  1. Connect as the privileged SYS user and run the following script. It creates a generic STUDENT user and grants minimalist privileges, then it creates a DAD (Data Access Descriptor), and authorizes the DAD. Don’t run the command if you’re actively using Oracle APEX on the default configuration of port 8080. It’s there for those folks you are running Tomcat on 8080.
-- This resets the default port so that it doesn't conflict with other environment.
EXECUTE dbms_xdb.SETHTTPPORT(8181);
 
-- This creates the STUDENT Data Access Descriptor.
EXECUTE dbms_epg.create_dad('STUDENT_DAD','/sampledb/*');
 
-- This authorizes the STUDENT_DAD
EXECUTE dbms_epg.authorize_dad('STUDENT_DAD','STUDENT');
  1. Connect as the STUDENT user and run the following script to create a PL/SQL Web Toolkit procedure.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE OR REPLACE PROCEDURE HelloWorld AS
BEGIN
  -- Set an HTML meta tag and render page.
  owa_util.mime_header('text/html');  -- <META Content-type:text/html>
  htp.htmlopen;                       -- <HTML>
  htp.headopen;                       -- <HEAD>
  htp.htitle('Hello World!');         -- <TITLE>HelloWorld!</TITLE>
  htp.headclose;                      -- </HEAD>
  htp.bodyopen;                       -- <BODY>
  htp.line;                           -- <HR>
  htp.print('Hello ['||USER||']!');   -- Hello [dynamic user_name]!
  htp.line;                           -- <HR>
  htp.bodyclose;                      -- </BODY>
  htp.htmlclose;                      -- </HTML>
END HelloWorld;
/
  1. Open a browser of your choice, and enter the following URL.
http://localhost:8181/sampledb/helloworld

You then see (or should see) the following Basic HTTP Authentication dialog box. Enter the STUDENT user as the User Name and the Password for the database account. Then, click the OK button.

XDB_BasicHTTPAuthentication

Provided you enter the User Name and Password correctly, you should see the following inside the browser’s display panel. The USER name is a system session scope variable, which will always return the owner of the package because its created as a Definers Rights procedure.

XDB_ProcedureDisplay

You have now successfully configured your Basic HTTP Authentication XDB, which may offer you some possibilities outside of using Oracle APEX.

Setting Up an Unsecured DAD

The trick here is building on what you did by eliminating the authentication. You do this by using the ANONYMOUS account, like Oracle’s APEX does. Well, not quite like it does because APEX provides a very good user authentication model. It allows you to connect to the ANONYMOUS user where you present and validate your credentials.

Since you have to do all the prior steps, these steps are numbered after those above. You start with step #4.

  1. Generally, the XML configuration is missing one key node that allows repository anonymous access. The missing node disallows anonymous login. You can run the code below as the SYS privileged user to open that up if its missing. You can check whether or not it’s missing by running this as the SYS user:
SQL> @?/rdbms/admin/epgstat.sql

If it returns the following as the last element of the output, you’ll need to run the PL/SQL block below.

+-------------------------------------------------------------------+
| ANONYMOUS access to XDB repository:                               |
|  To allow public access to XDB repository without authentication, |
|  ANONYMOUS access to the repository must be allowed.              |
+-------------------------------------------------------------------+
 
Allow repository anonymous access?
----------------------------------
false
 
1 row selected.

When you run this script, make sure you’re the privileged SYS user. Then, rerun the epgstat.sql script to verify that you’ve enabled anonymous access to the repository. You may also need to refresh your browser cache before retesting it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
SET SERVEROUTPUT ON
DECLARE
  lv_configxml XMLTYPE;
  lv_value     VARCHAR2(5) := 'true'; -- (true/false)
BEGIN
  lv_configxml := DBMS_XDB.cfg_get();
 
  -- Check for the element.
  IF lv_configxml.existsNode('/xdbconfig/sysconfig/protocolconfig/httpconfig/allow-repository-anonymous-access') = 0 THEN
    -- Add missing element.
    SELECT insertChildXML
           ( lv_configxml
           , '/xdbconfig/sysconfig/protocolconfig/httpconfig'
           , 'allow-repository-anonymous-access'
           , XMLType('<allow-repository-anonymous-access xmlns="http://xmlns.oracle.com/xdb/xdbconfig.xsd">'
       	   || lv_value
       	   || '</allow-repository-anonymous-access>')
       	   , 'xmlns="http://xmlns.oracle.com/xdb/xdbconfig.xsd"')
    INTO   lv_configxml
    FROM   dual;
 
    DBMS_OUTPUT.put_line('Element inserted.');
  ELSE
    -- Update existing element.
    SELECT updateXML
           ( DBMS_XDB.cfg_get()
           , '/xdbconfig/sysconfig/protocolconfig/httpconfig/allow-repository-anonymous-access/text()'
           , lv_value
           , 'xmlns="http://xmlns.oracle.com/xdb/xdbconfig.xsd"')
    INTO   lv_configxml
    FROM   dual;
 
    DBMS_OUTPUT.put_line('Element updated.');
  END IF;
 
  -- Configure the element.
  DBMS_XDB.cfg_update(lv_configxml);
  DBMS_XDB.cfg_refresh;
END;
/
  1. These tasks also require the privileged SYSTEM user account, and you should sign on to that account to run these commands. The first thing you may need to do is unlock the ANONYMOUS account. It is locked by default. After you unlock it, you’ll need to verify no default password was assigned by unassigning a password. The following two commands accomplish those tasks.
-- Unlock the user account.
ALTER USER anonymous ACCOUNT UNLOCK;
-- Ensure a password is assigned to the account so you can create a synonym later.
ALTER USER anonymous IDENTIFIED BY ANONYMOUS;
  1. These tasks require the privileged SYS user account because you’re going to create and authorize another DAD.
-- This creates the STUDENT_DB_DAD Data Access Descriptor.
EXECUTE dbms_epg.create_dad('STUDENT_DB_DAD','/db/*');
 
-- This authorizes the STUDENT_DB_DAD
EXECUTE dbms_epg.authorize_dad('STUDENT_DB_DAD','ANONYMOUS');
 
-- Open the anonymous account by setting the database-username parameter and value.
EXECUTE dbms_epg.set_dad_attribute('STUDENT_DB_DAD','database-username','ANONYMOUS');
  1. Connect as the STUDENT user and grant EXECUTE permissions on the HELLOWORLD procedure to the ANONYMOUS user account. The GRANT allows you to give unrestricted access to the ANONYMOUS account, which in turn provides it to your web audience.
SQL> GRANT EXECUTE ON helloworld TO anonymous;
  1. Connect as the ANONYMOUS user and create a local synonym that point to the STUDENT.HELLOWORLD procedure. The SYNONYM provides a program name for the URL statement. It’s hides the ownership of the actual procedure by supressing the schema name. (You may need to grant CREATE ANY SYNONYM as the SYSTEM user to the ANONYMOUS user.)
SQL> CREATE SYNONYM helloworld FOR student.helloworld;
After you’ve created the synonym, you want to remove the password from the ANONYMOUS account. The following syntax lets you do that as the privileged SYSTEM user.

SQL> ALTER USER anonymous IDENTIFIED BY NULL;
  1. Open a browser of your choice, and enter the following URL, which won’t require a User Name or Password.
http://localhost:8181/db/helloworld

You should see the same browser panel information as that shown by step #3 above, except one thing. The difference is the user name, which should now be ANONYMOUS. The execution occurs with the permissions of the invoker. This means you’ll see the data you’re allowed to see by the owning schema.

Written by maclochlainn

December 2nd, 2009 at 3:54 am

Oracle 11g XDB DADs

with 2 comments

Somebody asked me why the DBMS_EPG.GET_DAD_LIST is a procedure because you can’t just simply list the DAD values. I answered that Oracle chose to implement it that way. Then, they asked how they could query it. I suggested they just run the epgstat.sql diagnostic script provided in the $ORACLE_HOME/rdbms directory, which provides those values and much more.

You can run the diagnostic script as the SYS privileged user, or as any user that has been granted the XDBADMIN role, like this:

SQL> @?/rdbms/admin/epgstat.sql

Notwithstanding the diagnostic script, they asked how you could wrap the OUT mode PL/SQL data type in the procedure call, and return the list of values in a SQL query. Because the formal parameter is a PL/SQL data type, this requires two key things. One is a local variable that maps to the DBMS_EPG package collection data type, and a pipelined table function. Here’s one way to solve the problem:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
CREATE OR REPLACE TYPE dad_list AS TABLE OF VARCHAR2(4000);
/
 
CREATE OR REPLACE FUNCTION get_dbms_epg_dads
RETURN dad_list
PIPELINED IS
  -- Deine a local variable as the OUT mode target of GET_DAD_LIST procedure.
  SOURCE DBMS_EPG.VARCHAR2_TABLE;
  -- Declare a local variable of the SQL collection data type.  
  list DAD_LIST := dad_list(); 
BEGIN
  -- Call the procedure to populate the source.
  dbms_epg.get_dad_list(SOURCE);
  -- Extend space for all defined DAD values.
  list.EXTEND(SOURCE.COUNT);
  -- Assign values from PL/SQL collection to SQL collection.
  FOR i IN 1..source.COUNT LOOP
    list(i) := SOURCE(i);
    PIPE ROW(list(i));
  END LOOP;
  RETURN;
END get_dbms_epg_dads;
/
 
-- Set SQL*Plus width.
SET LINESIZE 79
 
-- Query collection.
SELECT column_value AS "DAD LIST"
FROM   TABLE(get_dbms_epg_dads);

Marco Gralike provided a simpler approach them the Pipelined Table Function here. I’ve copied the code example below:

1
2
3
4
5
6
SELECT u.dad AS "PL/SQL DAD List"
FROM   XMLTable(XMLNAMESPACES ( DEFAULT 'http://xmlns.oracle.com/xdb/xdbconfig.xsd' )
       , '/xdbconfig/sysconfig/protocolconfig/httpconfig/webappconfig/servletconfig/servlet-list/servlet[servlet-language="PL/SQL"]'
       PASSING DBMS_XDB.CFG_GET()
       COLUMNS DAD VARCHAR2(15)
       PATH '/servlet/servlet-name/text()') u;

Hope this proves handy to somebody else, too.

Written by maclochlainn

December 1st, 2009 at 10:46 pm

Black Screen of Death

without comments

Holiday Gift from Microsoft

Windows 7 ships. Then, we find it’s really Windows Vista+ (code base 6.1). Now, Microsoft give us a late year present, the Microsoft Black Screen of Death (their original post has been wiped by agreement with Microsoft it appears but only the shadow knows and rumors on the web). What better excuse to rush out and buy a MacBook Pro or use this fix?

Holiday Gift from Apple

Buying a MacBook Pro makes sense if you don’t already have one. Owning one, I’m hesitant to upgrade my MacBook Pro because the battery in the new one requires a service call when the battery wears out. An expensive item because the battery life is generally poor after 1,000 recharge cycles and that number of recharges may or may not occur before your Applecare service contract runs out.

Battery life/replacement is one of the reasons why I’ve stayed on my old MacBook Pro (purchased 16 months ago). The other reason is that I plan on getting by with a MacBook Pro for 4 to 5 years not Apple’s apparent plannned obsolescence of 3 years. While I’m in the gripe mode, the new Apple Cinema Display is attractive but not compatible with older MacBook Pro or MacBook computers. Also, the Altona DVI to Mini Display Port has mixed reviews out there and Apple seems disinterested in helping owners of older machines use the new Cinema screens. There aren’t any other alternatives to the Altona product (at least that I’ve found). I almost feel that somebody at Apple watched the movie Robots too often because it seems my 16 month old MacBook Pro is an outmode and there isn’t an upgrade option (only a new purchase).

Written by maclochlainn

December 1st, 2009 at 1:27 pm