Friday, October 10, 2014

Windows Powershell Script - write to both console and file

Few days back I was writing my first power shell script and thought to write my output to both console and some logfile. Surprisingly it wasn't as simple I thought and I ended up doing this.
function logMsg($msg)
{
    write-output $msg
    write-host $msg 
}
usage in script:
logMsg("My Error Msg")
logMsg("My Info Msg")
powershell script execution call:
ps> .\myFirstScript.ps1 >> testOutputFile.txt

here write-host is to write to console and write-output takes care of writing to my log file.

Friday, April 4, 2014

C# : HTML to PDF conversion

This is relatively free day today and I thought to utilize it by writing a bit :)

Sometime back I needed to create pdf from my html page, to do that I ended up using ITextSharp library which is a very decent library for this purpose. It doesn't provides much support for complex css, but is still very good for simple pages as was in my case.

Issue came when my html was using special symbol for less than equal to operator(≤). ITextSharp simply ignored it, so i came up with below solution that I found on a SO answer.

Solution: Use StyleSheet with font Arial while parsing html. Below is my method to convert html to PDF, code for style sheet is highlighted.
I used Arial font, but you might need some other depending on characters you need to support, so do a bit testing.



private MemoryStream CreatePdfStream(string html)
        {
            using (TextReader htmlReader = new StringReader(html))
            {
                string fontPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "Arial.TTF");
                StyleSheet style = new StyleSheet();
                if (File.Exists(fontPath))
                {
                    FontFactory.Register(fontPath);
                    style.LoadTagStyle("body", "face", "Arial");
                    style.LoadTagStyle("body", "encoding", BaseFont.IDENTITY_H);  
                }

                using (Document document = new Document())
                {
                    MemoryStream pdfStream = new MemoryStream();
                    PdfWriter pdfWriter = PdfWriter.GetInstance(document, pdfStream);
                    pdfWriter.CloseStream = false;
                    document.Open();

                    List<IElement> elements = HTMLWorker.ParseToList(htmlReader, style);
                    elements.ForEach(e => document.Add(e));
                    document.Close();
                    pdfStream.Position = 0;
                    return pdfStream;
                }
            }

        }

Monday, October 14, 2013

What volatile is actually good for

Today I was reading about something on SO and stumbled to this note about volatile keyword in C#, it seemed to be nice simple explanation of widely misunderstood term so I am sharing it here.

A good example is say you have 2 threads, one which always writes to a variable (say queueLength), and one which always reads from that same variable.
If queueLength is not volatile, thread A may write 5 times, but thread B may see those writes as being delayed (or even potentially in the wrong order).
A solution would be to lock, but you could also in this situation use volatile. This would ensure that thread B will always see the most up-to-date thing that thread A has written. Note however that this logic only works if you have writers who never read, and readers who never write, and if the thing you're writing is an atomic value. As soon as you do a single read-modify-write, you need to go to Interlocked operations or use a Lock.
I got this at below URL

Saturday, October 12, 2013

jQuery dynamic validation error messages

I am not a champ at asp.net mvc and found myself struggling with a task that at first I thought will be trivial, below is how I got through it :)

I wrote a custom validation attribute and needed to show validation messages as per values entered. Below is stripped down version of what I ended up doing:

Below lines add validation attribute named checkagainstmaxsumvalue that checks values of two fields and decides error messages dynamically

jQuery.validator.unobtrusive.adapters.add("checkagainstmaxsumvalue", ["dependentproperty1", "dependentproperty2"], function (options) {

    options.rules['checkagainstmaxsumvalue'] = {
        prop1: options.params['dependentproperty1'],
        prop2: options.params['dependentproperty2']
    };

});

jQuery.validator.addMethod("checkagainstmaxsumvalue", function (value, element, params) {
        var result = true;
        var prop1 = parseInt($('#' + params['prop1']).val(), 10);
        var prop2 = parseInt($('#' + params['prop2']).val(), 10);

        var errMsg = getErrorMessage(prop1, prop2);
        if (errMsg) {
            result = false;
        }
        return result;

    }, function (params, element) {
        var prop1 = parseInt($('#' + params['prop1']).val(), 10);
        var prop2 = parseInt($('#' + params['prop2']).val(), 10);
        return getErrorMessage(prop1, prop2);
    }
);

//Validation logic here, returns error message if validation fails 
//and empty string if success
function getErrorMessage(prop1, prop2) {
    var errMsg = "";
    if (prop1 < 20 && prop2 < 20) {
        errMsg = "Either of prop1 and prop2 should be greater than 20";
    } else if (prop1 + prop2 > 30) {
        errMsg = "Sum of prop1 and prop2 should be less than equal to 30";
    }
    return errMsg;

}

This all I did after reading below answer at every developer's life saver site SO :)
http://stackoverflow.com/questions/13352626/dynamic-jquery-validate-error-messages-with-addmethod-based-on-the-element#answer-13352987

This worked perfectly fine in my case but still there is concern that validation logic executes twice, once for deciding message and once for actual verification. This sounds a bit overkill but till now I didn't have a better option.

Thursday, July 18, 2013

Copy modules as reference from one DNN page to another using SQL script

If (you are a dev in hurry with a TL waiting for your output)
    Then simply skip to script at bottom of this page, edit variables in first three lines as per your requirement and test results.
else
    Keep Reading :)

Is it difficult it to hold a glass of water for a minute! No, not at all. But how will it be if you are supposed to hold it for 100 hrs :) This post is about tackling one such case.

Problem statement:
Copy modules as reference from one DNN page to another using SQL script.

One of my friend who administers multiple DNN portals, was having around 7-8 html modules on a page and was copying them across some 50 odd existing pages I guess. Copying modules using dnn UI is pretty simple as described here, but is very slow as you have to do it one by one and every-time it performs a certain set of steps. So he asked me to devise some alternative and i came-up with below.

Solution:

DNN keeps track of modules and their instances in below tables:

  • dbo.DesktopModules and dbo.ModuleDefinitions - These tables track what all modules are installed in site.
  • dbo.Modules - This table tracks instances of modules added on pages, means an entry is created in this table when you add a new instance of a module on some page
Apart from above three tables, there is another table named dbo.TabModules which keeps track of info that which module is placed on which tab(In dnn teminology, a tab represents a page). This table gets one entry for every instance of module whether it is reference copy of an existing module or added a fresh.

So after knowing above bit of information, I coded a small script which takes three parameters as below
name of source page  : from where to copy
name of target page   : where to paste 
list of module titles     : that need to be copied from source page to target page

With these parameters my script simply creates an entry in dbo.TabModules table and updates VersionGuid column for related modules for every entry of module title.


      *  name of a DNN page can be retrieved through page settings
    **  module title can be duplicate. Though script i wrote is smart enough to point you on such cases but I still advise to choose unique module titles while copying modules
  ***  I have also used a function "udf_List2Table" to get table from pipe delimited list of module. This function can be found here at my favorite SQL blog.  
****  This script creates copy of existing modules, means changing content in one will automatically update that in other.

-------------------Script goes below------------------------

DECLARE @CopyFromPage VARCHAR(1000) = 'Test Page'       --U can get this from page settings
DECLARE @CopyToPage VARCHAR(1000) = 'Child Page 5'
DECLARE @OriginalModuleTitles VARCHAR(MAX) = 'ModuleA||ModuleB||ModuleC||ModuleD||ModuleE' --Hoping u have added only one module with this title on from page


DECLARE @OriginalModuleTitle VARCHAR(1000)
DECLARE @TabIdFromPage INT
DECLARE @TabIdToPage INT
DECLARE @ModuleId INT
DECLARE @HostUserId INT

SELECT @TabIdFromPage = TabID FROM dbo.Tabs WHERE TabName = @CopyFromPage
SELECT @TabIdToPage = TabID FROM dbo.Tabs WHERE TabName = @CopyToPage

IF(@TabIdFromPage > 0 AND @TabIdToPage > 0)
BEGIN

       --Convert pipe delimited module names to table
       DECLARE @tempTable TABLE (moduleName varchar(1000))
       INSERT INTO @tempTable
       SELECT * FROM udf_List2Table(@OriginalModuleTitles, '||')

       WHILE EXISTS (SELECT * FROM @tempTable)
       BEGIN
              SELECT TOP 1 @OriginalModuleTitle = moduleName FROM @tempTable

              IF EXISTS (SELECT * FROM dbo.TabModules WHERE TabID = @TabIdToPage AND ModuleTitle = @OriginalModuleTitle)
                     PRINT 'Module with title ''' + @OriginalModuleTitle + ''' already exists in page ''' + @CopyToPage + ''''
              ELSE
              BEGIN
      
                     IF ((SELECT COUNT(*) FROM dbo.TabModules WHERE TabID = @TabIdFromPage AND ModuleTitle = @OriginalModuleTitle) = 1)
                     BEGIN

                           SELECT @ModuleId = ModuleID FROM dbo.TabModules WHERE TabID = @TabIdFromPage AND ModuleTitle = @OriginalModuleTitle
                           SELECT @HostUserId = UserID FROM dbo.Users WHERE Username = 'host'
             
                           DECLARE @NewVersionGuid UNIQUEIDENTIFIER = NEWID()
                           DECLARE @CreatedOnDate DATETIME = GETDATE()
             
                           INSERT INTO [dbo].[TabModules]
                                  ([TabID], [ModuleID], [PaneName], [ModuleOrder], [CacheTime], [Alignment], [Color], [Border], [IconFile],
                                  [Visibility], [ContainerSrc], [DisplayTitle], [DisplayPrint], [DisplaySyndicate], [IsWebSlice], [WebSliceTitle],
                                  [WebSliceExpiryDate], [WebSliceTTL], [CreatedByUserID], [CreatedOnDate], [LastModifiedByUserID],
                                  [LastModifiedOnDate], [IsDeleted], [CacheMethod], [ModuleTitle], [Header], [Footer], [CultureCode],
                                  [UniqueId], [VersionGuid], [DefaultLanguageGuid], [LocalizedVersionGuid])
             
                           SELECT
                                  @TabIdToPage, [ModuleID], [PaneName], [ModuleOrder], [CacheTime], [Alignment], [Color], [Border], [IconFile],
                                  [Visibility], [ContainerSrc], [DisplayTitle], [DisplayPrint], [DisplaySyndicate], [IsWebSlice], [WebSliceTitle],
                                  [WebSliceExpiryDate], [WebSliceTTL], @HostUserId, GETDATE(), @HostUserId,
                                  GETDATE(), [IsDeleted], [CacheMethod], [ModuleTitle], [Header], [Footer], [CultureCode],
                                  NEWID(), @NewVersionGuid, [DefaultLanguageGuid], [LocalizedVersionGuid]
                           FROM [dbo].[TabModules]
                           WHERE TabID = @TabIdFromPage AND ModuleTitle = @OriginalModuleTitle

                           --Update version Guids for related modules
                           UPDATE dbo.TabModules SET VersionGuid = @NewVersionGuid WHERE ModuleID = @ModuleId

                           PRINT 'Module Copied Successfully.'

                     END
                     ELSE
                           PRINT 'Module title you entered either doesn''t exists in from page or you have multiple modules with this title on from page.'

              END

              DELETE FROM @tempTable WHERE moduleName = @OriginalModuleTitle
       END

       PRINT 'You need to clear site cache [Host >> Host Settings >> Clear Cache] to view results.'

END
ELSE
       PRINT 'Either or both of Copy to/from page name is incorrect'


-------------------Script ends here------------------------ 


 You need to clear your DNN cache to view results of this script on your portal.




Saturday, May 18, 2013

HTML source code wrongly appended to streamed file using Response.Write

Today while writing a small web application where I needed to download a datatable as csv file. I faced issue of page html getting wrongly appended to file being downloaded. I was using Response.Write to download file on client machine.

Solution to my problem was easy, I just needed to add an extra line telling content-length. Below is method that I finally ended up using.


        private void DownloadDataAsCsv(DataTable dt)
        {
            string tab = "";
            StringBuilder sb = new StringBuilder();
            foreach (DataColumn dc in dt.Columns)
            {
                sb.Append(tab + dc.ColumnName);
                tab = ",";
            }
            sb.Append("\n");
            int i;
            foreach (DataRow dr in dt.Rows)
            {
                tab = "";
                for (i = 0; i < dt.Columns.Count; i++)
                {
                    sb.Append(tab + dr[i].ToString());
                    tab = ",";
                }
                sb.Append("\n");
            }
            Response.ClearHeaders();
            Response.ClearContent();
            Response.ContentType = "application/vnd.ms-excel";
            Response.AddHeader("content-disposition", "attachment; filename=Export.csv");
            Response.AddHeader("Content-Length", sb.ToString().Length.ToString());
            Response.Write(sb.ToString());
            Response.Flush();
            Response.End();
        }

Wednesday, February 27, 2013

User does not have required permissions. Verify that sufficient permissions have been granted and Windows User Account Control restrictions have been addressed

Today I spent quite some time getting rid of a common SSRS error due to a silly mistake, so here I am with it. This might save a bit of your time on a bad day

Error I faced was,
User does not have required permissions. Verify that sufficient permissions have been granted and Windows User Account Control (UAC) restrictions have been addressed
Solution,
Add desired users to administrator group on SSRS server
Mistake I did was googling problem straightaway and crying for help before I started thinking myself. With quick google, I got a few suggestions which didn't helped me as they were pointing to same error but occurring possibly due to something else. So here is what helped me, you might need to call IT team for getting this done.

Do login on your SSRS server and open edit local users and groups



 This will open a window where you can add/remove users from a group

  • Select Groups in left pane, this will give you list of groups on your server
  • Now double click Administrators, this will give you a screen like below
  • Here click on Add button and enter name of user to whom you wanna give access
  • Press ok, then apply and there you are. 
  • Problem solved :)  
  • If this doesn't solves your cause, check this nice blogpost from caleb



"swarg jaana ho to khud he marna padta hai", a hindi proverb which traversed my mind when IT guy failed to help me on this :P

Thanks,
Ravi

Friday, February 22, 2013

Can´t upload a new attachement in DNN 6 with forum 5.0.3

Today one of my friend pinged me to help on an issue with DNN forum module(05.00.03), he was using it on DNN 06.01.05. Problem was "Upload new attachment failing repeatedly with some exception". He was getting below exception:

Error: is currently unavailable. DotNetNuke.Services.Exceptions.ModuleLoadException: The underlying system threw an exception. ---> DotNetNuke.Services.FileSystem.FolderProviderException: The underlying system threw an exception. ---> System.ArgumentNullException: Value cannot be null. Parameter name: content at DotNetNuke.Services.FileSystem.StandardFolderProvider.AddFile(IFolderInfo folder, String fileName, Stream content) at DotNetNuke.Services.FileSystem.FileManager.MoveFile(IFileInfo file, IFolderInfo destinationFolder) --- End of inner exception stack trace --- at DotNetNuke.Services.FileSystem.FileManager.MoveFile(IFileInfo file, IFolderInfo destinationFolder) at DotNetNuke.Common.Utilities.FileSystemUtils.MoveFile(String strSourceFile, String strDestFile, PortalSettings settings) at DotNetNuke.Modules.Forum.WebControls.AttachmentControl.cmdUpload_Click(Object sender, EventArgs e) --- End of inner exception stack trace ---

So as usual firstly I googled the error and found that many people are having similar error, but none of them were having a solution so I thought that it might be some permissions issue and messed up with folder permissions, but all failed.

So finally I debugged the code and found that line that was breaking was calling method "FileSystemUtils.MoveFile" which got deprecated in DNN 6 

So I replaced it with method call for "RenameFile" and there you go!!

Below is the culprit line under method cmdUpload_Click in file "AttachmentControl.ascx.vb"
FileSystemUtils.MoveFile(ParentFolderName + FileName, ParentFolderName + destFileName, PortalSettings)

 and here is its replacement

DotNetNuke.Services.FileSystem.FileManager.Instance.RenameFile(DotNetNuke.Services.FileSystem.FileManager.Instance.GetFile(DotNetNuke.Services.FileSystem.FolderManager.Instance.GetFolder(PortalId, BaseFolder), FileName), destFileName)


If you are also having this error, then just download the module's source code from codeplex, open it in visual studio, replace lines as I mentioned, build this module. Now go to your filesystem, grab new DLL for forum module and replace it in your DNN apps bin directory.

Sorry I forgot to write earlier, please take backup before doing this to your production site.

Hope this will help!!

Thanks,
Ravi

Monday, January 14, 2013

Hello World SSRS Report using BIDS 2008

Below is a simple step by step illustration for creating a Simple SSRS report using Buisness Intelligence Development Studio 2008

  • Create a new SSRS project on BIDS (you can find it under Business Intelligence tab under installed templates)
  • You will get something like this in a new project, I have added a couple of Shared data sources and sample report (you won't get them obviously ;)
  • Now right click on "Shared Data Source" folder icon and select add new data source
    • This will prompt you with a dialog box to enter server, userid, password, etc. Get these things filled and you will get a new shared data source ready to be used. Something similar to SampleDBDataSource.rds as in above image
    • You can skip this step if you dont wanna create a new shared data source. you can later embed this in report directly.
  • Next click on "Reports" folder and add new report, this will launch Report Wizard. At first step you need to select an existing datasource or create a new one. 

  • Next step you need to enter your query. For my sample it’s something like below:
  • Enter your desired query or stored procedure name (using query builder) and follow the wizard to reach “Design The Table” screen, as below
  • At this step you have to divide your available fields in  Group and Details section (just drag and drop), for my sample report I did like below:

  • Now follow the wizard and you will have a report ready. In my case it was something like below:

  • Press preview tab to preview how your report looks like.
  • Now if you can make any design changes needed. Lets say we want to add string "Employee Name: " before Employee Name displyed(as in preview above)
    • For this we need to add expression in place of Employee Name.
      • Right Click textbox you want expression to be added(Emp Name textbox in this case)
      • Select "Expression" from the context menu, you will get a screen as below. Enter desired expression and press OK(See below).
  • This will give you below result.

Hey we are done with this post. Enjoy!! :)


Edit 15th Jan, 2013

I wrote this post to answer one of the question on codeproject and that question wants to show employee date once per group, so here are the steps to do so:
  1. Add a new row below employee name row, as shown in below image. 

  2. Add expression for Employee date, you may just select DOB field as shown below or can add some expression as shown a couple of images above. I used below expression in my sample
    • ="DOB: " + Fields!Employee_DOB.Value

  3. and there you are!
I hope you guys won't mind some of my employee being minor DOB(1/8/2013) ;)

Thanks,
Ravi