BPSoftware.com
Home   Utilities   Purchase   FAQ   Support   Contact        
Shareware Utilities
 APrintDirect
 AIconExtract
 AFile Attribute Manager
Freeware Utilities
 AddrMon
 AFileSync
 ASysIcon
 B&P Table Utilities
 BPACLer
 BPSNMPMon
 BPSNMPUtil
 CharCount
 Delphi® Components
 MacAddr
Miscellaneous
 BPSoftware Blog
 Purchase Shareware
 Support

 Subscribe!

Tuesday, November 25, 2008
SQL: DBCC CHECKTABLE on multiple tables

Part of decent SQL database maintenence is periodically checking the integrity, both logical and physical, of database objects. SQL Server has the DBCC CHECKDB command that will check the integrity of the objects and report back the results. DBCC CHECKDB runs CHECKALLOC, CHECKTABLE and CHECKCATALOG on the specified database.



DBCC CHECKTABLE allows you to check the integrity of a specified table or indexed view. If there are a large number of tables to check, the task of running the command for each tablename could be time consuming. Creating a script that loops through a set of database tables and runs DBCC CHECKTABLE for each, is a bit easier.

The sys.objects contains each of the user-defined schema objects in a database. Rows that are of [Type] = 'U' are tables. This makes it easy to get all the tables within a database (of course other filters can be applied). The following script will loop through and run the DBCC CHECKTABLE command for each table in the database (the command can be easily replaced to perform another action on each returned table):
1
2-- set options
3SET NOCOUNT ON;
4SET ANSI_PADDING ON;
5SET ANSI_NULLS ON;
6SET QUOTED_IDENTIFIER ON;
7
8
9-- declare table to store table names
10-- in SQL 2005 you can use table variables
11-- if you do not need or want to store the data you can store it in a table
12-- variable instead of a temporary table
13DECLARE @tablenames TABLE (
14 TableName VARCHAR(255)
15);
16
17
18
19-- populate table with tablenames
20INSERT INTO @tablenames
21 SELECT
22 name
23 FROM
24 sys.objects
25 -- additional filters can be set to narrow table selection
26 WHERE
27 ([type] = 'U');
28
29
30
31-- Declare a cursor to loop through table
32DECLARE tablenames INSENSITIVE SCROLL CURSOR
33 FOR
34 SELECT
35 TableName
36 FROM
37 @tablenames;
38
39
40
41
42-- Open the cursor.
43OPEN tablenames
44
45
46
47-- declare variables
48DECLARE @curtablename AS VARCHAR(255);
49DECLARE @cmd AS VARCHAR(1024);
50
51
52
53-- Loop through all the tables
54WHILE (1=1)
55 BEGIN
56 FETCH NEXT FROM tablenames INTO @curtablename;
57 IF @@fetch_status <> 0 BREAK;
58 -- run the command
59 set @cmd = 'DBCC CHECKTABLE ([' + rtrim(@curtablename) + '])';
60 exec (@cmd)
61 print @cmd + CHAR(10) + CHAR(13);
62 end
63
64
65
66-- Close cursor
67CLOSE tablenames;
68DEALLOCATE tablenames;
69

Labels: ,

posted by Brad Prendergast at 9:04:00 PM (0 comments)
Links to this post
Permalink
Monday, November 24, 2008
SQL: Index Fragmentation Maintenance

In a previous post, I discussed SQL Server's built in ability for suggesting missing indexes. As records are inserted, updated and deleted from tables indexes can become fragmented. A fragmented index affects performance (table scans and index seeks) when the DBMS needs to search for records.

This probably leaves the question, "How can one defragment the indexes of SQL tables?"

Within SQL Server 2005 and greater you can setup a maintenance plan to defragment all the indexes within all or a particular database. You can also right click on an index and select to either Rebuild or Reorganize and index (rebuilding will re-create the index whereas reorganize reorders the index pages). I prefer to have a little more control and use the following script as part of my maintenance plan. This script allows for a little more control in the selecting of tables, indexes and databases to maintain. I tend to rebuild indexes that are more than 30% fragmented and reorganize those that are greater than 5%.

SQL Server 2005 (and greater) has index information more readily accessible. The key to this script is the index information that is contained in sys.dm_db_index_physical_stats. This table returns the size and fragmentation information for each index.
1/** **/
2-- Create table to hold index information
3SET NOCOUNT ON;
4
5create table _IndexInformation (
6 database_id smallint,
7 ObjectName varchar(255),
8 [object_id] int,
9 IndexName varchar(255),
10 [index_id] int,
11 index_level int,
12 index_type_desc varchar(60),
13 avg_fragmentation_in_percent float,
14 fragment_count float,
15 page_count bigint,
16 record_count bigint,
17 index_depth int
18);
19/** **/

1/** **/
2-- clear table contents
3truncate table _IndexInformation;
4
5-- populate table
6insert into _IndexInformation
7 select
8 database_id,
9 object_name(i.object_id) AS [ObjectName],
10 i.[object_id],
11 i.name AS [IndexName],
12 dm.index_id,
13 dm.index_level,
14 dm.index_type_desc,
15 dm.avg_fragmentation_in_percent,
16 dm.fragment_count,
17 dm.page_count,
18 dm.record_count,
19 dm.index_depth
20 from
21 -- database, table and index information can be specified
22 /*
23 sys.dm_db_index_physical_stats (
24 { database_id NULL 0 DEFAULT }
25 , { object_id NULL 0 DEFAULT }
26 , { index_id NULL 0 -1 DEFAULT }
27 , { partition_number NULL 0 DEFAULT }
28 , { mode NULL DEFAULT }
29 )
29 This example uses database 1; you should replace this with the ID of the database you wish to process
30 */

31 sys.dm_db_index_physical_stats(1,null,null,null,'detailed') dm
32 INNER JOIN sys.indexes i ON
33 (i.object_id = dm.object_id) AND (i.index_id = dm.index_id);
34/** **/
35

1/** **/
2-- read index table and maintain indexes
3SET NOCOUNT ON;
4SET ANSI_PADDING ON;
5SET ANSI_NULLS ON;
6SET QUOTED_IDENTIFIER ON;
7
8
9-- Declare variables.
10declare @tablename as varchar(255)
11declare @indexname as varchar(255)
12declare @frag as float
13declare @cmd as varchar(1024)
14
15-- Declare a cursor.
16declare indexes insensitive scroll cursor
17for
18 select
19 ObjectName,
20 IndexName,
21 avg_fragmentation_in_percent
22 from
23 _IndexInformation
24 where
25 (avg_fragmentation_in_percent >= 5);
26
27-- Open the cursor.
28open indexes
29
30
31-- Loop through all the indexes.
32while (1=1)
33 begin
34 fetch next from indexes into @tablename, @indexname, @frag;
35 if @@fetch_status <> 0 break;
36 -- if fragmentation is above 30% rebuild index
37 -- if fragmentation is above 5% reorganize index
38 if (@frag >= 30)
39 begin
40 set @cmd = 'ALTER INDEX [' + rtrim(@indexname) + '] ON [' + @tablename +'] REBUILD;'
41 exec (@cmd)
42 print @cmd;
43 end
44 else
45 if (@frag >= 5)
46 begin
47 set @cmd = 'ALTER INDEX [' + rtrim(@indexname) + '] ON [' + @tablename +'] REORGANIZE;'
48 exec (@cmd)
49 print @cmd;
50 end;
51 end
52
53
54
55-- Close cursor
56close indexes;
57deallocate indexes;
58/** **/
59

1/** **/
2-- delete index table
3drop table _IndexInformation;
4/** **/
5


I have the script schedule to as part of my database maintenance plan. I run the script several times with additional selection criteria limiting it to groups of tables in a particular database.

Labels: ,

posted by Brad Prendergast at 9:51:00 PM (2 comments)
Links to this post
Permalink
Thursday, November 20, 2008
SQL: Where are the database files?

From within the SQL Server Management Studio you can right click on a database and view its properties to get a list of the physical files that make up a database. If you do not have access to SQL Management Studio and the database is online you can run the sp_helpfile stored procedure. The sp_helpfile stored procedure lists the files for the current (individual) database; the database must be online. What about retrieving file information for offline databases?
Another alternative for retrieving the list of database files for a single or collective set of databases is to query the sys.master_files table. You can query the sys.master_files database if the database is online, offline or in one of the other states. The following query retrieves database file information:
1SELECT
2 database_id,
3 DB_Name(database_id) as database_name,
4 [file_id],
5 type_desc,
6 [name],
7 physical_name,
8 state_desc,
9 [size],
10 max_size
11FROM
12 sys.master_files
13WHERE
14 -- you can use the DB_ID function to return the
15 -- database id of a database by name
16 (database_id = DB_ID(N'master'))
17 -- if you know the id you can filter by it directly
18 -- (database_id = 1)
19ORDER BY
20 type_desc DESC,
21 physical_name;




The sys.databases table has information about the database in the current SQL Server instance.

1SELECT
2 database_id,
3 [name],
4 compatibility_level,
5 collation_name,
6 state_desc
7FROM
8 sys.databases;


Labels: ,

posted by Brad Prendergast at 9:08:00 PM (0 comments)
Links to this post
Permalink
Wednesday, November 12, 2008
SQL: Remove / Delete Orphan Users

As discussed in the post SQL Delete/Drop a User from each Database, when you delete a Login from SQL Server it does not remove them from the databases they have explicit permissions on. If a user login is removed, and they are not removed from the database you would have orphan users with permission to the database. The stored procedure listed will remove (delete) orphan datatabase user logins.


1 USE [master]
2 GO
3 SET ANSI_NULLS ON
4 GO
5 SET QUOTED_IDENTIFIER ON
6 GO
7
8 create procedure [dbo].[sp_DeleteOrphanUsers]
9 AS
10
11 -- revoke orphan user
12 declare @cmd as varchar(1000)
13 declare @uname as sysname
14
15 DECLARE my_cur INSENSITIVE SCROLL CURSOR
16 FOR
17 -- orphan user
18 select u.name from master..syslogins l right join
19 sysusers u on l.sid = u.sid
20 where l.sid is null and issqlrole <> 1 and isapprole <> 1
21 and (u.name <> 'INFORMATION_SCHEMA' and u.name <> 'guest' and u.name <> 'sys'
22 and u.name <> 'system_function_schema')
23 order by u.name
24
25 open my_cur
26
27 WHILE (1 = 1)
28 begin
29 FETCH FROM my_cur INTO @uname
30 IF @@fetch_status <> 0
31 BREAK
32 set @cmd = 'exec sp_revokedbaccess ''' + rtrim(@uname) + ''''
33 print @cmd
34 exec (@cmd)
35
36 end
37 DEALLOCATE my_cur
38
39 return(0)

The procedure would be executed like the following:

1 use [databasename] /* where databasename is the name of the database to run the procedure on */
2 exec sp_DeleteOrphanusers

Labels: , ,

posted by Brad Prendergast at 6:04:00 AM (2 comments)
Links to this post
Permalink
Tuesday, November 11, 2008
SQL Delete/Drop a User from each Database

Deleting a user from SQL server deletes them from the security system, however it does not remove them from databases they have 'securables' (permissions speficified) for. The following procedure will remove a user from each database and remove their account.


1 USE [master]
2 GO
3 SET ANSI_NULLS ON
4 GO
5 SET QUOTED_IDENTIFIER ON
6 GO
7 CREATE procedure [dbo].[sp_DeleteUser]
8 @username sysname = NULL
9 AS
10
11 -- declare @nam as sysname
12 declare @cmd1 as varchar(500)
13
14 if @username is not null
15 begin
16 set @cmd1 = 'PRINT ''?'' USE ? DROP USER ' + @username
17 exec sp_msforeachdb @command1= @cmd1
18 exec ('DROP LOGIN ' + @username)
19 return(0)
20 END
21 return (1)


The stored procedure is called like;

1 exec sp_DeleteUser 'loginname' /* where loginname is the name of the account that you would like deleted */

Labels: , ,

posted by Brad Prendergast at 8:45:00 PM (0 comments)
Links to this post
Permalink
Monday, November 10, 2008
SQL Optimization: Am I missing any indexes? - Part I

Lately, I have been doing a lot with SQL (2005) optimization.  Database design plays an important role in the efficiency of a database.  A key part of the design is the use of indexes.  Indexes significantly affect the performance of a database whether it is during a SELECT, INSERT, UPDATE and DELETE Transact-SQL (DML) statement.  The biggest gain from an index is when a user SELECTs data from a database.   The designer of the database and application should have done their best to come up with indexes that accommodate how the database will be used.  How can one be sure they’re not missing any indexes?

SQL Server 2005 has a feature that keeps track of Missing Index Information.  This information is contained in the sys.dm_db_missing_index_groups, sys.dm_db_missing_index_group_stats, sys.dm_db_missing_index_details tables.   The missing index information in these tables can be used to determine indexes that would improve database performance based upon its usage.  The following query returns the indexes that would improve database performance.  Before creating the indexes review the usage values to determine if the creation of the index is really necessary.



1 /* missing indexes*/
2 use [master]
3 SELECT IG.INDEX_GROUP_HANDLE,
4 ID.INDEX_HANDLE,
5 ID.DATABASE_ID,
6 DB_NAME(ID.DATABASE_ID) AS DATABASE_NAME,
7 ID.[OBJECT_ID],
8 OBJECT_NAME(ID.[OBJECT_ID],ID.DATABASE_ID) as TABLE_NAME,
9 ID.STATEMENT AS FULL_TABLE_NAME,
10 ID.EQUALITY_COLUMNS,
11 ID.INEQUALITY_COLUMNS,
12 ID.INCLUDED_COLUMNS,
13 GS.USER_SEEKS,
14 GS.USER_SCANS,
15 GS.LAST_USER_SEEK,
16 GS.LAST_USER_SCAN,
17 GS.AVG_TOTAL_USER_COST,
18 GS.AVG_USER_IMPACT,
19 GS.SYSTEM_SEEKS,
20 GS.SYSTEM_SCANS,
21 GS.LAST_SYSTEM_SEEK,
22 GS.LAST_SYSTEM_SCAN,
23 GS.AVG_TOTAL_SYSTEM_COST
24 FROM SYS.DM_DB_MISSING_INDEX_DETAILS AS ID
25 INNER JOIN SYS.DM_DB_MISSING_INDEX_GROUPS AS IG
26 ON ID.INDEX_HANDLE = IG.INDEX_HANDLE
27 INNER JOIN SYS.DM_DB_MISSING_INDEX_GROUP_STATS AS GS
28 ON GS.GROUP_HANDLE = IG.INDEX_GROUP_HANDLE
29 ORDER BY ((GS.AVG_TOTAL_USER_COST * GS.AVG_USER_IMPACT) * (GS.USER_SEEKS + GS.USER_SCANS)) DESC
30
31


Using the above query result you could create the necessary index (the field names are listed where their values should be used):


1 CREATE NONCLUSTERED INDEX <unique index name>
2 ON <FULL_TABLE_NAME> (<EQUALITY_COLUMNS>,<INEQUALITY_COLUMNS>) -- exclude INEQUALITY_COLUMNS if NULL
3 INCLUDE (<INCLUDED_COLUMNS>); -- exclude INCLUDED_COLUMNS if NULL

The information in these tables is dynamic and When SQL Server is restarted, all of the missing index information is dropped from them.    The index information is collected over time; therefore it is better to query this information when the server has been ‘used for a period of time’

 

Labels: , ,

posted by Brad Prendergast at 6:36:00 AM (0 comments)
Links to this post
Permalink
Thursday, November 22, 2007
A little System.Diagnostics

I am not going to get overly verbose here; however the importance of application logging is immeasurable. When applications are deployed, obtaining ‘real life’ application can assist in diagnostic and overall application health situations. Excessive logging can be ineffective (too much to analyze) and burdensome (logging to a text file that grows forever) if not implemented appropriately.

When developing and deploying an application having the flexibility of what, when and how to log can alleviate the burden and ineffectiveness of logging. The System.Diagnostics namespace has a number of classes available that allow for this type of flexible logging. The applied use of these classes allows a developer to specify what to log in an application at various levels of detail within one application without the need to deploy diagnostic builds. Depending on diagnostic needs, the application’s config file can specify the level of diagnostic information to write and where. A simple Logging class (this is the class I created to use this functionality) can be developed as follows:


1namespace BPSoftware
2{
3 public class Logging
4 {
5 /// <summary>
6 /// strNameSpace is the base namespace for use in the logging message.
7 /// </summary>

8 public static string strNameSpace;
9
10 /// <summary>
11 /// GeneralTraceSwitch is a traceswitch object for logging. Switch level is set through
12 /// the application config switches section. The destination listener is also set through
13 /// the application config file.
14 /// </summary>

15 public static TraceSwitch GeneralTraceSwitch = new TraceSwitch( "General", "General Trace Switch" );
16
17 Methods
86 }

87}

Throughout the application logging messages can be added:
1 catch ( Exception ex)
2 {
3 Logging.WriteLineTraceError( "ClassName", "Method", "Exception: " + ex.Message );
4 }

5
6 Logging.WriteLineTraceInfo( "ClassName", "Method",,
7 String.Format( "{0}: {1}", e.ActionType.ToString(), e.Message )
8 );


The application’s config file can then be altered to determine the appropriate level and location of the logging based upon certain diagnostic needs. The switches node specifies what level of logging should occur. The switches operate in a hierarchial fashion (3 includes 3, 2 and 1; 2 includes 2 and 1):
1 <system.diagnostics>
2 <trace autoflush="true" indentsize="4">
3 <!-- Specify listener output -->
4 <listeners>
5 <remove name="Default" />
6 <!-- Write to a log file -->
7 <add name="Default"
8 type="System.Diagnostics.TextWriterTraceListener"
9 initializeData="AFileSync.log" />
10 <!-- Write to the Event Log-->
11 <!-- <add name="Default"
12 type="System.Diagnostics.EventLogTraceListener"
13 initializeData="Application" /> -->
14 </listeners>
15 </trace>
16 <switches>
17 <!-- Off = 0, Error = 1, Warning = 2, Info = 3, Verbose = 4 -->
18 <add name="General" value="3" />
19 </switches>
20 </system.diagnostics>


Using the application’s config file to specify the level of logging allows for the capture of diagnostic information of a stable build to capture ‘real world’ information. The flexibility of the listener to write to (you can create customer listeners) is an added bonus!

Labels: , , ,

posted by Brad Prendergast at 8:49:00 AM (0 comments)
Links to this post
Permalink
Sunday, October 07, 2007
Control and List Windows Services

As I whipped up my file synchronizing service application, to synchronize files between my computer and my NAS), I worked with the System.ServiceProcess Namespace. The System.ServiceProcess Namespace "provides classes that allow you to implement, install, and control Windows service applications".

My file synchronization needs did not require that my application service to be "running" all the time. In, fact I only needed the service to run under certain situations. It was easy enough to get the service written and installed and now I wanted to 'automatically' (programmatically) start and stop my service when certain conditions were met.

Entrance - the ServiceController Class. The ServiceController Class "Represents a Windows service and allows you to connect to a running or stopped service, manipulate it, or get information about it." In order to get acclimated to the ServiceController Class I created a simple application to list and display information about the installed services.


1using System;
2using System.Windows.Forms;
3using System.ServiceProcess;
4
5namespace Services1
6{
7 public partial class Form1 : Form
8 {
9 ServiceController controller;
10
11 public Form1()
12 {
13 controller = new ServiceController();
14 controller.MachineName = ".";
15 InitializeComponent();
16 lstServices.DisplayMember = "DisplayName";
17 lstServices.ValueMember = "ServiceName";
18 lstServices.DataSource = ServiceController.GetServices();
19 }

20
21 private void StartService(string servicename)
22 {
23 controller.ServiceName = servicename;
24 controller.Start();
25
26 }

27
28 private void PauseService(string servicename)
29 {
30 controller.ServiceName = servicename;
31 if (controller.CanPauseAndContinue)
32 controller.Pause();
33 }

34
35 private void StopService(string servicename)
36 {
37 controller.ServiceName = servicename;
38 if (controller.CanStop)
39 controller.Stop();
40 }

41
42
43 private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
44 {
45 if (lstServices.SelectedItems.Count > 0)
46 {
47 try
48 {
49 controller.ServiceName = lstServices.SelectedValue.ToString();
50 textBox1.Text = String.Format(
51 "Service Name: {0}\r\nDisplay Name: {1}\r\nType: {2}\r\nStatus: {3}",
52 controller.ServiceName,
53 controller.DisplayName,
54 controller.ServiceType.ToString(),
55 controller.Status.ToString()
56 );
57 }

58 catch (Exception ex)
59 {
60 textBox1.Text = String.Format("Error: \r\n{0}",ex.Message);
61 }

62 }

63 else
64 {
65 textBox1.Clear();
66 }

67 }

68
69 private void btnStart_Click(object sender, EventArgs e)
70 {
71 StartService(lstServices.SelectedValue.ToString());
72 }

73
74 private void btnPause_Click(object sender, EventArgs e)
75 {
76 PauseService(lstServices.SelectedValue.ToString());
77 }

78
79 private void btnStop_Click(object sender, EventArgs e)
80 {
81 StopService(lstServices.SelectedValue.ToString());
82 }

83 }

84}

Labels: , ,

posted by Brad Prendergast at 3:12:00 PM (0 comments)
Links to this post
Permalink
Monday, October 01, 2007
Let's Synchronize Some Files

Recently, I posted about the new addition to my backup strategy; a NAS. My intended use of this device is to have a hot backup of my files. The system I was using before was not capable of storing the amount of data I need backed up, nor was it easy to manage. I modified the set of xCopy batch files that I used for my backups to write the files to the NAS.

This system worked fine, but I needed more. I manage a lot of data and have a lot of files. I wouldn't want to lose any in the event of a system failure. The one problem I see with using this process is the lack of a real time solution.

I was in search of a solution that would offer me real-time synchronization between my files and the NAS. I didn't have to search very far. The .NET Framework includes the FileSystemWatcher Class.

The FileSystemWatcher Class "Listens to the file system change notifications and raises events when a directory, or file in a directory, changes." (quoted directly from MSDN) After reading through this class I quickly whipped up a solution. I developed my own FileSync class that allowed me to easily synchronize files between my remote and local storage. I created a Windows Service (threaded) built around my class (the screen shot in this post is from a sample app I used to test my class) so that I could have the FileSync class running and keeping my files in sync without me needing to do anything. Here is the unit I created:


1using System;
2using System.IO;
3using System.Diagnostics;
4
5namespace BPSoftware
6{
7 class FileSync
8 {
9 private FileSystemWatcher watcher;
10
11 Fields
40
41 Constructors
62
63 Methods
115
116 Events
165 }

166}

167

Labels: , ,

posted by Brad Prendergast at 1:09:00 AM (0 comments)
Links to this post
Permalink
Sunday, September 23, 2007
DSOFile and Summary Properties

Yesterday I posted about using ShellExecuteEx to display the document properties dialog for a file or folder. Displaying the properties dialog is nice, however I also need to access the OLE document properties (Document Summary Properties), the information that is stored on the Summary and Custom tabs of the properties dialog. I did quite a bit of searching, reading and testing in an attempt to find a simple solution. I did get to learn a lot about the Document Summary Properties and it looked like my only option was to head down the road using the IPropertyStorage Interface. As I was testing some code I came across the best little ActiveX component that can quickly access the property information. The Dsofile.dll is an ActiveX component is available from Microsoft. They even include a sample application (although the samples are in VB6 and VB7 they are useable). This component simplifies (I like things simple) setting and getting the document summary properties. The KB article and download mention that this is meant for Office files, however I have found that it works fine on other files as well (the exception being that the Office documents seem to be the only types of files with Custom Properties). The other great thing about this component is that "You have a royalty-free right to use, to modify, to reproduce, and to distribute the Dsofile.dll sample file component and the C++ source code files in any way you find useful." That quote is taken directly from the DSOfile.dll Article ID: 224351 that is listed on Microsoft's Support site. My thoughts, why reinvent the wheel when you really don't need to. I highly recommend using this component if you need to access the extended properties. As usual, I whipped up a sample application testing this components functionality. The meager sample code is listed here (image of application screen is also included in this post):

1 public partial class Form1 : Form
2 {
3 public Form1()
4 {
5 InitializeComponent();
6 }

7
8 private void btnSelectFile_Click(object sender, EventArgs e)
9 {
10 if (openFileDialog1.ShowDialog() == DialogResult.OK)
11 {
12 textBox1.Text = openFileDialog1.FileName;
13 toolTip1.SetToolTip(textBox1, textBox1.Text);
14 lstSummary.Items.Clear();
15
16 OleDocumentPropertiesClass odpc = new OleDocumentPropertiesClass();
17 odpc.Open(
18 @openFileDialog1.FileName,
19 false,
20 dsoFileOpenOptions.dsoOptionOpenReadOnlyIfNoWriteAccess
21 );
22 // Summary "Standard" Properties
23 ListViewItem lvTitle = new ListViewItem();
24 lvTitle.Group = lstSummary.Groups[0];
25 lvTitle.Text = "Title";
26 lvTitle.SubItems.Add(odpc.SummaryProperties.Title);
27 lstSummary.Items.Add(lvTitle);
28
29 ListViewItem lvSubject = new ListViewItem();
30 lvSubject.Group = lstSummary.Groups[0];
31 lvSubject.Text = "Subject";
32 lvSubject.SubItems.Add(odpc.SummaryProperties.Subject);
33 lstSummary.Items.Add(lvSubject);
34
35 ListViewItem lvCategory = new ListViewItem();
36 lvCategory.Group = lstSummary.Groups[0];
37 lvCategory.Text = "Category";
38 lvCategory.SubItems.Add(odpc.SummaryProperties.Category);
39 lstSummary.Items.Add(lvCategory);
40
41 ListViewItem lvKeyword = new ListViewItem();
42 lvKeyword.Group = lstSummary.Groups[0];
43 lvKeyword.Text = "Keywords";
44 lvKeyword.SubItems.Add(odpc.SummaryProperties.Keywords);
45 lstSummary.Items.Add(lvKeyword);
46
47 ListViewItem lvComments = new ListViewItem();
48 lvComments.Group = lstSummary.Groups[0];
49 lvComments.Text = "Comments";
50 lvComments.SubItems.Add(odpc.SummaryProperties.Comments);
51 lstSummary.Items.Add(lvComments);
52
53 ListViewItem lvAuthor = new ListViewItem();
54 lvAuthor.Group = lstSummary.Groups[1];
55 lvAuthor.Text = "Author";
56 lvAuthor.SubItems.Add(odpc.SummaryProperties.Author);
57 lstSummary.Items.Add(lvAuthor);
58
59 ListViewItem lvRevision = new ListViewItem();
60 lvRevision.Group = lstSummary.Groups[1];
61 lvRevision.Text = "Revision Number";
62 lvRevision.SubItems.Add(odpc.SummaryProperties.RevisionNumber);
63 lstSummary.Items.Add(lvRevision);
64
65 // Custom Properties
66 foreach (DSOFile.CustomProperty cp in odpc.CustomProperties)
67 {
68 ListViewItem lvc = new ListViewItem();
69 lvc.Group = lstSummary.Groups[2];
70 lvc.Text=cp.Name;
71 lvc.SubItems.Add(cp.get_Value().ToString());
72 lstSummary.Items.Add(lvc);
73 }

74 odpc.Close(false);
75 }

76 }

77 }

This is something I'd also like to port to Delphi for use in those applications. Maybe this week sometime......

Labels: , ,

posted by Brad Prendergast at 6:52:00 PM (0 comments)
Links to this post
Permalink
Saturday, September 22, 2007
ShellExecuteEX and ShellExecuteInfo Revisited

Who says history doesn't repeat itself? I am working on a project that needs to display and access file properties. A quick search through the .NET 2.0 Framework didn't yield anything that popped out. I did however, remember our wonderful friend - the SHELL32 API. Previously, I had posted on using ShellExecuteEX with Delphi (I am a huge Delphi fan; I may not be posting much about it recently, but I did not forget it). Here is that same sample in using C#.


1 public const uint SW_SHOW =0x5;
2 public const uint SEE_MASK_INVOKEIDLIST = 0xC;
3
4 public enum verbs
5 {
6 properties,
7 open,
8 edit,
9 explore,
10 print
11 }

12
13 [StructLayout(LayoutKind.Sequential)]
14 public struct SHELLEXECUTEINFO
15 {
16 public int cbSize;
17 public uint fMask;
18 public IntPtr hwnd;
19 public String lpVerb;
20 public String lpFile;
21 public String lpParameters;
22 public String lpDirectory;
23 public uint nShow;
24 public int hInstApp;
25 public int lpIDList;
26 public String lpClass;
27 public int hkeyClass;
28 public uint dwHotKey;
29 public int hIcon;
30 public int hProcess;
31 }

32
33 [DllImport("shell32.dll")]
34 static extern bool ShellExecuteEx(ref SHELLEXECUTEINFO lpExecInfo);
35
36 public void MyShellExecuteInfo(string filename, verbs verb)
37 {
38 SHELLEXECUTEINFO info = new SHELLEXECUTEINFO();
39 info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(info);
40 switch (verb)
41 {
42 case verbs.properties:
43 info.lpVerb = "properties";
44 break;
45 case verbs.print:
46 info.lpVerb = "print";
47 break;
48 case verbs.open:
49 info.lpVerb = "open";
50 break;
51 case verbs.explore:
52 info.lpVerb = "explore";
53 break;
54 case verbs.edit:
55 info.lpVerb = "edit";
56 break;
57 }

58
59 info.lpFile = filename;
60 info.nShow = SW_SHOW;
61 info.fMask = SEE_MASK_INVOKEIDLIST;
62 ShellExecuteEx(ref info);
63 }

64

Labels: , ,

posted by Brad Prendergast at 7:26:00 PM (0 comments)
Links to this post
Permalink
Monday, September 17, 2007
A ToolStripMenuItem and a Check

One nice addition to the .NET 2.0 framework is the ToolStripMenuItem Class. The ToolStripMenuItem Class replaces the MenuItem Class. The ToolStripMenuItem has a Checked property that gets or sets a value indicating if the item is checked or not. Coupled with the ToolStripMenuItem.CheckOnClick property, this can be useful for identifying which item has been selected or activated. Unfortunately, there isn't some sort of 'allow only one checked' property which allows for only one item in a menu to be 'Checked' at a time. This functionality can be easily added in code (or you could derive your own class that has this functionality; a nice addition to a personal control library). Create an OnClick and OnCheckChanged method that are referenced by each of the ToolStripMenuItems. Basically, you check to see if the current item is checked or not and then toggle the check for the other menu items. You could also check another property of the sender (such as the Tag property) to perform some other action.

Here is an example:

1 private void CheckedIconsToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
2 {
3 if (sender is ToolStripMenuItem)
4 {
5 if (!((ToolStripMenuItem)sender).Checked) return;
6 foreach (ToolStripMenuItem item in (((ToolStripMenuItem)sender).GetCurrentParent().Items))
7 {
8 if (item != null && item != sender && item.Checked)
9 {
10 item.Checked = false;
11 return;
12 }

13 }

14 }

15 }

16
17 private void CheckedToolStripMenuItem_Click(object sender, EventArgs e)
18 {
19 if (sender is System.Windows.Forms.ToolStripMenuItem)
20 {
21 if (!((ToolStripMenuItem)sender).Checked)
22 {
23 ((ToolStripMenuItem)sender).Checked = !((ToolStripMenuItem)sender).Checked;
24 }
;
25 switch (((ToolStripMenuItem)sender).Tag.ToString())
26 {
27 case "1":
28 break;
29 case "2":
30 break;
31 case "3":
32 break;
33 case "4":
34 break;
35 case "5":
36 break;
37 default:
38 break;
39 }
;
40 }

41 }

42

Labels: , ,

posted by Brad Prendergast at 9:10:00 PM (0 comments)
Links to this post
Permalink
Sunday, September 16, 2007
A little FileIconInit for the System Image list

I can hear the theme for Welcome Back Kotter as I write this. It seems that things have been quiet on here for quite sometime, however those that know me can attest to the fact that life away from here has been anything but that. Thankfully, things have returned to a point where a lot more content should find its way onto this world.

As things progress, I am doing a substantial amount of development using C# and the .NET Framework. I still continue to develop new utilities that satisfy my specific needs. I also try to update some of the available utilities. The most recent update has been to ASysIcon (this application was redeveloped using the .NET Framework). I've done quite a bit of work with icons in the past (also take a look at AIconExtract) and ASysIcon is a utility that displays the images found in the system image list. The system image list contains the icons that are associated with the different registered file types on a computer.

The retrieval of the system image list is a straight forward process. There are a few unmanaged API calls that work quite well for this task. One thing that may not be so apparent is that the the images retrieved will be those images that are cached in the system image list. If the system hasn't had a need to load them, well, they won't be in the list. This could pose some concern if you would like to retrieve the entire list. The trick is to get all the system images cached. Actually, this isn't really a trick. With little searching the FileIconInit function surfaces. This function will initialize or reinitialize this image list. FileIconInit has a boolean parameter determines if it should restore the image cache from disk. Passing true as this parameter will load all of the images, not just the recently cached ones. The following sample code shows how to display the system image list in a ListView:


1using System;
2using System.Windows.Forms;
3
4 using System.Runtime.InteropServices; // http://support.microsoft.com/kb/319350
5
6namespace SysIcons1
7{
8 public partial class Form1 : Form
9 {
10 [StructLayout(LayoutKind.Sequential)]
11 public struct SHFILEINFO
12 {
13 public IntPtr hIcon;
14 public IntPtr iIcon;
15 public uint dwAttributes;
16 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
17 public string szDisplayName;
18 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
19 public string szTypeName;
20 }
;
21 //Retrieve the handle to the icon that represents the file and the index of the icon within the system image list
22 public const uint SHGFI_ICON = 0x100;
23
24 // Retrieve the index of a system image list icon.
25 public const uint SHGFI_SYSICONINDEX = 0x4000;
26
27 // Large icon
28 public const uint SHGFI_LARGEICON = 0x0;
29
30 // Small icon
31 public const uint SHGFI_SMALLICON = 0x1;
32
33 public const uint LVM_FIRST = 4096;
34 public const uint LVSIL_NORMAL = 0;
35 public const uint LVSIL_SMALL = 1;
36 public const uint LVM_SETIMAGELIST = LVM_FIRST + 3;
37 // need this style on Windows 9x
38 public const uint LVS_SHAREIMAGELISTS = 0x0040;
39
40 // Retrieves information about an object in the file system, such as a file, folder, directory, or drive root.
41 [DllImport("shell32.dll")]
42 public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
43
44 // The SendMessage function sends the specified message to a window or windows.
45 [DllImport("user32.dll")]
46 private static extern UInt32 SendMessage(IntPtr hWnd, UInt32 Msg,
47 UInt32 wParam, UInt32 lParam);
48
49 // Destroys an icon and frees any memory the icon occupied.
50 [DllImport("User32.dll")]
51 private static extern int DestroyIcon(System.IntPtr hIcon);
52
53 // Initializes or reinitializes the system image list.
54 // FileIconInit is not included in a header file. You must call it directly from Shell32.dll, using ordinal 660.
55 [DllImport( "Shell32.dll", EntryPoint = "#660")]
56 private static extern bool FileIconInit( bool fRestoreCache );
57
58 // Retrieves the number of images in an image list.
59 [DllImport( "comctl32.dll")]
60 private static extern int ImageList_GetImageCount( IntPtr himl);
61
62 public Form1()
63 {
64 InitializeComponent();
65 }

66
67 private void Form1_Load(object sender, EventArgs e)
68 {
69 IntPtr hImgSmall; //handle to the small system image list
70 IntPtr hImgLarge; //handle to the large system image list
71 SHFILEINFO shinfo = new SHFILEINFO();
72
73 FileIconInit(true);
74
75 //Small Icons
76 hImgSmall = SHGetFileInfo("", 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_SYSICONINDEX SHGFI_SMALLICON);
77
78 //Large Icons
79 hImgLarge = SHGetFileInfo("", 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_SYSICONINDEX SHGFI_LARGEICON);
80
81 // Set Icon Images
82 SendMessage(listView1.Handle, LVM_SETIMAGELIST, LVSIL_SMALL, (uint)hImgSmall.ToInt32());
83 SendMessage(listView1.Handle, LVM_SETIMAGELIST, LVSIL_NORMAL, (uint)hImgLarge.ToInt32());
84
85 for ( int i = 0; i < ImageList_GetImageCount(hImgLarge) ; i++ )
86 {
87 listView1.Items.Add(i.ToString(),i);
88 }

89 }

90
91 private int Get_Image_Index(string filename)
92 {
93 IntPtr hImgSmall;
94 SHFILEINFO shinfo = new SHFILEINFO();
95
96 hImgSmall = SHGetFileInfo(filename, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo.GetType()), SHGFI_ICON SHGFI_SMALLICON);
97 DestroyIcon(shinfo.hIcon);
98 return (int)shinfo.iIcon;
99 }

100 }

Labels: , ,

posted by Brad Prendergast at 9:55:00 PM (0 comments)
Links to this post
Permalink
Saturday, March 03, 2007
A DataGridView, DataGridViewCheckBoxColumn, DataGridViewButtonColumn and DataColumn.

As I continue to work on moving (upgrading?) applications and code to dotNET 2.0 I continue to find that in some cases certain implemenations have made changes that simplify things. One case in particular, is the displaying of CheckBoxes and Buttons in a DataGrid. One old way was to override the painting of a cell. Another might have been to overaly a control in the clipped rectangle of the DataGrid.

As with any “upgrade” new “things” are introduced. In this case I will refer to the DataGridView, DataGridViewCheckBoxColumn and the DataGridViewButtonColumn classes. The allows for the displaying of “data” in a grid (Note: when working with large amounts of data don’t forget to set the VirtualMode property). This source of the data can be anything that implements a IList, IListSource, IBindingList or IBindingListView interface. The data displayed doesn’t always have to be from a database, a one dimmensional array alos works well with a datagrid (take a look at the sample method PopulateDataGridView). It may sound intimidating, but this is all fairly straght forward.
1namespace DrawInDataGrid1
2{
3 public partial class Form1 : Form
4 {
5 private DataTable myTable;
6 private DataGridViewCheckBoxColumn checkcolumn;
7 private DataGridViewButtonColumn buttoncolumn;
8 private DataColumn colItem1, colItem2, colItem3;
9 private DataRow NewRow;
10 private DataView myView;
11
12 public Form1()
13 {
14 InitializeComponent();
15 }

16
17 private void Form1_Load(object sender, EventArgs e)
18 {
19 // DataTable to hold data that is displayed in DataGrid
20 myTable = new DataTable("myTable");
21
22 // the three columns in the table
23 colItem1 = new DataColumn("CheckBox", Type.GetType("System.Boolean"));
24 colItem2 = new DataColumn("Button", Type.GetType("System.String"));
25 colItem3 = new DataColumn("String", Type.GetType("System.String"));
26
27 // add the columns to the table
28 myTable.Columns.Add(colItem1);
29 myTable.Columns.Add(colItem2);
30 myTable.Columns.Add(colItem3);
31
32 checkcolumn = new DataGridViewCheckBoxColumn(false);
33 checkcolumn.HeaderText = "CheckBox";
34 checkcolumn.DataPropertyName = "CheckBox";
35
36 buttoncolumn = new DataGridViewButtonColumn();
37 buttoncolumn.HeaderText = "Button";
38 buttoncolumn.DataPropertyName = "Button";
39
40 dataGridView1.Columns.Add(checkcolumn);
41 dataGridView1.Columns.Add(buttoncolumn);
42
43
44 // Fill in some data
45 NewRow = myTable.NewRow();
46 NewRow[0] = true;
47 NewRow[1] = "0";
48 NewRow[2] = "Test";
49 myTable.Rows.Add(NewRow);
50
51 NewRow = myTable.NewRow();
52 NewRow[0] = false;
53 NewRow[1] = "1";
54 NewRow[2] = "Next One";
55 myTable.Rows.Add(NewRow);
56
57 // DataView for the DataGridView
58 myView = new DataView(myTable);
59 myView.AllowDelete = false;
60 myView.AllowEdit = false;
61 myView.AllowNew = false;
62
63 // add an event to check for button click
64 this.dataGridView1.CellContentClick +=
65 new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_CellContentClick);
66
67 // Assign DataView to DataGrid
68 dataGridView1.DataSource = myView;
69 }

70
71
72 private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
73 {
74 if (sender is DataGridView)
75 {
76 if ((((DataGridView)sender).Columns[e.ColumnIndex] is DataGridViewButtonColumn) &&
77 (e.RowIndex >= 0))
78 {
79 MessageBox.Show(e.RowIndex.ToString());
80 }

81 }

82 }

83 }

84}



Another “column” I am glad they added is the DataGridViewImageColumn.

Labels: , ,

posted by Brad Prendergast at 1:52:00 PM (1 comments)
Links to this post
Permalink
Sunday, February 04, 2007
System Information and the PerformanceCounter Class

The other day I discussed retrieving system information using the .NET framework. I guess the main focus of that post was to discuss using PInvoke to call unmanaged WIN32 DLL exports, more so than actually retrieving different pieces of system information. The objective of that post was retrieving memory information (I also retrieve disk information in my ‘AboutBox’ discussed in that post). In the 'managed codeworld' there is another way to get different information values. Using the System.Diagnostics.PerformanceCounter Class you can retrieve ‘counter’ information from a local or remote machine. Many of you may be familiar with some of the information accessible through PerformanceCounters if you run the
performance monitor control panel applet.
The information accessible via the System.Diagnostics.PerformanceCounter Class is not limited to just system information. Many services and applications also make ‘counter’ information available for retrieval. Performance Counters are broken out by category (System.Diagnostics.PerformanceCounterCategory). Within each category there many be multiple instances, for example the Logical Disk category will have an instance for each drive, as well as an instance for the _Total of all drives. Each instance will have its own set of counters that can be retrieved. In keeping with the same example, the _Total instance of the Logical Disk category has counters such as ‘% Free Space’, ‘Free Megabytes’ and ‘% Disk Write Time’ to name a few.
Reading counter information is a lot easier than one would expect (the .NET Framework almost made things too easy). I had created a basic-sample PerformanceCounter application that allows for the selection of one of the available (on the local machine) performance counter categories. Once the category is selected, each instance is listed (if there is more than one). Once the counter to be retrieved is selected the value is displayed in a System.Windows.Forms.ListBox (using a System.Timers.Timer for interval). Note: When I use Delphi for development I use the TChart component to display graphs (as I had in BPSNMPMon). If anyone knows of a decent charting control that works with Visual Studio 2005 let me know.


1 System.Diagnostics.PerformanceCounter perfcounter;
2
3 private void cbxPerfCategories_SelectedIndexChanged(object sender, EventArgs e)
4 {
5 cbxInstances.Enabled = true;
6 cbxInstances.Items.Clear();
7 cbxCounters.Items.Clear();
8 lstOutput.Items.Clear();
9 timer1.Enabled = false;
10
11 if (cbxPerfCategories.SelectedIndex != -1)
12 {
13 if (PerformanceCounterCategory.Exists(cbxPerfCategories.Text))
14 {
15 PerformanceCounterCategory perfcategory =
16 new PerformanceCounterCategory(cbxPerfCategories.Text);
17
18 cbxInstances.Items.AddRange(perfcategory.GetInstanceNames());
19 if (cbxInstances.Items.Count == 0)
20 {
21 cbxInstances.Enabled = false;
22 // Get the counters for a category that has only one
23 // instance
24 GetCounters(perfcategory, "",cbxCounters);
25 }

26 }

27 }

28 }

29
30 private void GetCounters(PerformanceCounterCategory perfcategory,
31 string instancename,
32 ComboBox combobox)
33 {
34 System.Diagnostics.PerformanceCounter[] counters;