Invoke PowerShell with parameters from C#

In this example, I’m using PowerShell script to connect to Exchange Server and create a mailbox. The parameters include the credentials that’ll be passed from C# while calling the script.

Below is the PowerShell script:

$global:creds;
$global:cName;
$global:fName;
$global:lName;
$global:UPN;
$global:alias;
$global:database="db_name";
$global:sAMAccountName;
$global:OU;
$global:pwd;
$global:displayName;
Invoke-Command -ComputerName serverdns.mylab.com  -Credential $creds -ScriptBlock {
$s = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://{serverdns}/PowerShell/ -Authentication Kerberos -Credential $using:creds
Import-PSSession $s
$securePassword = ConvertTo-SecureString $using:pwd -AsPlainText -Force
new-mailbox -UserPrincipalName $using:UPN -Alias $using:alias -Database $using:database -Name $using:cName -SamAccountName $using:sAMAccountName -OrganizationalUnit $using:OU -Password $securePassword -FirstName $using:fName -LastName $using:lName -DisplayName $using:displayName -ResetPasswordOnNextLogon $true
}

The C# code below will invoke the above PowerShell script:

using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Security;

public string CreateMailbox(Int64 requestID, int sID, string cName, string firstName, string lastName, string displayName, string UPN, string alias, string exchangeDatabase, string userName, string OU, string accountPassword)
{
	Uri uri = new Uri(exch.ExchangeServer);
	SecureString secureExchangePassword = String2SecureString(exch.ExchangePassword);

	PSCredential credentials = new PSCredential(exch.ExchangeUserName, secureExchangePassword);

	Runspace runspace = RunspaceFactory.CreateRunspace();

	Collection<PSObject> results = new Collection<PSObject>();

	PowerShell powershell = PowerShell.Create();

	try
	{
		runspace.Open();
		powershell.Runspace = runspace;

		//Change the Path to the Script to suit your needs
		System.IO.StreamReader sr = new System.IO.StreamReader("Script\\MailboxScript.ps1");
		powershell.AddScript(sr.ReadToEnd());

		powershell.Runspace.SessionStateProxy.SetVariable("creds", credentials);
		powershell.Runspace.SessionStateProxy.SetVariable("cName", cName);
		powershell.Runspace.SessionStateProxy.SetVariable("fName", firstName);
		powershell.Runspace.SessionStateProxy.SetVariable("lName", lastName);
		powershell.Runspace.SessionStateProxy.SetVariable("UPN", UPN);
		powershell.Runspace.SessionStateProxy.SetVariable("alias", alias);
		powershell.Runspace.SessionStateProxy.SetVariable("database", exchangeDatabase);
		powershell.Runspace.SessionStateProxy.SetVariable("sAMAccountName", userName);
		powershell.Runspace.SessionStateProxy.SetVariable("OU", OU);
		powershell.Runspace.SessionStateProxy.SetVariable("password", accountPassword);
		powershell.Runspace.SessionStateProxy.SetVariable("displayName", displayName);

		results = powershell.Invoke();
		if (powershell.Streams.Error.Count > 0)
		{
			StringBuilder sb = new StringBuilder();
			foreach (ErrorRecord er in powershell.Streams.Error)
				sb.Append(er.ToString() + "       ");

			LogFactory.LogError("RequestID:'" + requestID.ToString() + "' ID:'" + sID.ToString() + "' Error:  On creating exchange account(PowerShell). Reason: " + sb.ToString());
			return "";
		}
		else
		{
			sr.Close();
			sr.Dispose();
			return String.Concat("CN=" + cName + ",", OU);
		}
	}
	catch (Exception ex)
	{
		LogFactory.LogError("RequestID:'" + requestID.ToString() + "' ID:'" + sID.ToString() + "' Error:  While creating exchange account. Reason: " + ex.Message.ToString());
		return "";
	}
	finally
	{
		runspace.Dispose();
		runspace = null;
	}
}

private SecureString String2SecureString(string password)
{
	SecureString remotePassword = new SecureString();
	for (int i = 0; i < password.Length; i++)
		remotePassword.AppendChar(password[i]);

	return remotePassword;
}

This C# code can be called from an Application passing the required Exchange Server information and required Parameter values. The using statements in the C# code mentions the required Nuget packages to be installed.

Delete Geckodriver temp folders using Powershell

Geckodriver is an executable that interacts with Firefox installed on the System to run automated tests by running the Firefox instance either fully or in ghost-mode.
Geckodriver creates profiles in the Windows temp folder that can be of huge size depending on the Profile created for the Firefox driver.
The folder name is of the format “rust_mozprofile*”.

The following Powershell script will delete such folders by searching for this string format under C:\windows\temp and also creates logs for the deleted folders.

$datestring = (Get-Date).ToString("s").Replace(":","-")

Start-Transcript C:\logs\Mozprofile_LogPurgeTranscript_$datestring.txt -NoClobber
get-childitem -path c:\windows\temp | where{$_.LastWriteTime -lt (get-date).AddDays(-30) -and $_.name -like "rust_mozprofile*" -and $_.psiscontainer -eq $true} |Sort-Object lastwritetime -Descending| select -last 100 |% {remove-item -Recurse $_.fullname -force -Verbose -Confirm:$false}
Stop-Transcript

This PowerShell script can also be used to generally search for folders and matching a string a delete them.

How to Count number of e-mails in mailbox using PowerShell

First, you need to access the mailbox on the Microsoft Exchange Server and then get the mailbox statistics for the count of e-mails. The credentials you use should have admin access on the Server to be able to access the mailbox.

The following script uses some credentials stored in a file on the machine where the script is running and connects to a Session on the Microsoft Exchange Server:

$un = “admin@somedomain.com”
$Pass = cat “C:\Data\Securestring.txt” | ConvertTo-SecureString
$Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Un, $Pass

$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://{servername}/PowerShell/ -Authentication Kerberos -Credential $Credentials

Import-PSSession $Session -DisableNameChecking

$data=Get-MailboxFolderStatistics -Identity "somemail@somedomain.com" -FolderScope inbox | ?{$_.folderpath -eq "/inbox"} | select ItemsInFolder 

Write-Host $data.ItemsInFolder

You can further use this data item to mail it to the Admins for Operations purpose.

To know how to send e-mail in PowerShell, check out this post here.

Unable to find type System.Windows.MessageBox error PowerShell

If you’re using a Message Box in your PowerShell script, chances are you’re running into this error while running the script in a PowerShell window.
But this error does not occur in PowerShell ISE.

If you generally run the command in PowerShell as below and you get the error:

[System.Windows.MessageBox]::Show("Test box")

Then try adding the Type using the below command and run the above command again:

Add-Type -AssemblyName PresentationFramework
[System.Windows.MessageBox]::Show("Test box")

The above type is part of the PresentationFramework.dll that was added with WPF.

The other type is the System.Windows.Forms which was added with Windows Forms, and exists within the Windows Forms assemblies.

So, add the type as below:

Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.MessageBox]::Show("Test box")

You can add these types in your script as per your need. If you’re script uses Params, just add the Add-Type line below it.

Test emails on Server with SMTP

You can check whether the port is open on a Server by using tcping.exe which you can download online. Tcping can also be similarly used to check other ports on a Server.

Open Powershell or Command prompt in Admin mode and then type the following command:

tcping smtp.some.domain 25

Port 25 is usually the default port for SMTP communication between mail servers.

You can also telnet to test out this port. Telnet should be enabled on your machine.

telnet smtp.some.domain 25

You can send a test email using Powershell using the below command:

Send-MailMessage -From 'Test User1 <test.user1@test.com>' -To 'Test User <test.user2@test.com>' -Subject 'Test mail' -SmtpServer 'smtp.some.domain'

Continuous Deployment execute database scripts with TFS

In another post, we’ve learned how we can use a Build definition and Release Management in a CI/CD pipeline. Please go through the articles if you need more information on these topics.

In this post, I’ll explain how we can use Release Definition to execute database scripts as part of the continuous deployment strategy. We will add a task to execute PowerShell script to achieve this.

Create Release definition

The task to be added is PowerShell under Utility. This task should be added before the Application files are deployed in the CD pipeline.

Create a Release definition for your Release pipeline as shown below, the path for the PowerShell script should be a shared path and the database Server is accessible.

Add the below code snippet to the PowerShell script. The script should be modified as per the requirement.

$localScriptRoot = "C:\Scripts"
$Server = "dbserver"
$scripts = Get-ChildItem $localScriptRoot | Where-Object {$_.Extension -eq ".sql"} | Sort-Object -Property Name
$qt = 0
foreach ($s in $scripts)
    {
        Write-Host "Running Script : " $s.Name -BackgroundColor DarkGreen -ForegroundColor White
        $script = $s.FullName	
        Invoke-Sqlcmd -ServerInstance $Server -InputFile $script -username "TFSBuildUser" -password "****" -querytimeout $qt
    }
Write-Verbose "scripts executed successfully" -verbose

The scripts to be executed are present at the location C:\Scripts with extension “.sql”. The password can be also passed as an argument to the PowerShell script and added to the Release Definition secret variable.

Use the Triggers tab to link to the required Build definition of your Project so that the Release definition is integrated with the Build pipeline. The linking can also be done using the Artifacts tab.

You can setup multiple environments using this strategy like Dev, POC and Production.

Move files based on Date Time using PowerShell

This post shows how we can use PowerShell to move files from one folder to another say while Archiving the files.

$SrcFolder = "C:\Files"
$DestFolder = "C:\Backup\2017"
$count = 0

 if(-not (Test-Path $DestFolder)){
        New-Item -Path $DestFolder -ItemType Directory
 }
 else{
    Write-Host $DestFolder "Path exists"
 }

 Get-ChildItem -Path $SrcFolder | Where-Object {$_.LastWriteTime -lt (Get-Date).AddMonths(-13)} | ForEach-Object {
    
    Write-Host "Moving: " $SrcFolder"\"$_ $_.LastWriteTime to $DestFolder
    Move-Item -Path $SrcFolder"\"$_ -Destination $DestFolder
    $count = $count + 1
 }

 Write-Host "Moved" $count "items."

The above code takes each item found in the Source Folder and checks LastWriteTime say for 13 months earlier, and moves the file to the Destination folder.

The command used to Move the files is Move-Item which takes -Path and -Destination as parameters.

We have the following tasks that requires to be done on a Windows Server 2012 R2 for this example using PowerShell:

  1. Archive Files from a Source Folder to Destination Folder (could be a Shared folder on the Network) based on time-stamp, only for files older than 2 years in this example.
  2. It takes 2 parameters, Year parameter is mandatory. Destination Folder is optional. Both Source and Destination are however hard-coded into the script.
  3. The script creates a folder with Year as folder-name at the Destination and Archive all the files for that year in the created folder.
  4. A log file is created in the same path on which the script is running under the Logs folder. All Archiving activity including any errors faced for any files are logged.
  5. The progress of the Archiving Files is also shown in the Console.
  6. Errors are handled like no files or folder found for the Year Parameter.
  7. The user running the script has to be an Administrator on the Machine. The Script has to be running in the Admin Mode.
param(
      [Parameter(Mandatory=$True)]
      [int]$Year,      
      [Parameter()]
      [String]$DestinationFolder
      )


Add-Type -AssemblyName System.Windows.Forms

$SourceFolder = "C:\TestSource"
$Date=((Get-Date).AddYears(-2).Year)

if ($Year -gt $Date) 
     {
     [System.Windows.Forms.MessageBox]::Show("Only Process the documents before Year $Date")
     break
     }

if($DestinationFolder -eq "")
  {
  $DestinationFolder = "\\TestDestinationFolder"
  } 

If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
[System.Windows.Forms.MessageBox]::Show("Please run powershell application as administator")
break
}

$i=0
$DirectoryChain=(Get-ChildItem -Path $SourceFolder -Recurse)
$DirCount=$DirectoryChain.Count
$time=Get-Date
$DirectoryChain |
ForEach-Object {

$FileYear = (Get-Date($_.LastWriteTime)).Year

      if($FileYear -eq $Year){

        ########Creating Log File############
        if(!(Test-Path -Path ".\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt"))
           { 
           Write-Host "Starting Data Archiving Process for $Year" -BackgroundColor Green        
           New-Item -Path ".\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt" -ItemType File -Value "Archival Process Started at $(Get-Date)
==================================================================================================================================
"          
            if(!(Test-Path -Path ".\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt")){[System.Windows.Forms.MessageBox]::Show("Log File could not created and exiting script")
               break}
            }
        
         try {

           if(Test-Path -Path "$DestinationFolder\$FileYear")
               {  
               Move-Item -LiteralPath $_.FullName -Destination "$DestinationFolder\$FileYear" -ErrorAction Ignore
               "Moved '$_' to $DestinationFolder\$FileYear successfully at $(Get-Date)" | add-content ".\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt"
               $i++
               }
            else
               {
               New-Item -Path "$DestinationFolder\$FileYear" -ItemType Directory -ErrorAction Ignore
               Move-Item -LiteralPath $_.FullName -Destination "$DestinationFolder\$FileYear"
               "Moved '$_' to $DestinationFolder\$FileYear successfully at $(Get-Date)" | add-content ".\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt"
               $i++
               }

           $Remaining=$DirCount-$i
           [int]$PercentageStat=(($i/$DirCount)*100)
           
           Write-Progress -Activity "Archival Document Process" -Status "Archiving Document $_  $i" -PercentComplete $PercentageStat
           #Start-Sleep -Seconds 2


            }
         Catch
            {
             "Error in moving object $_.FullName" | add-content ".\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt"
            }
          }

}


######Checking for existence of log file and appending comment in log file"########
if(Test-Path -Path ".\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt")
{"===================================================================================================================================
Archival Processs Finished at $(Get-date)" | add-content ".\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt"

Write-Host "Data Archiving Process Finished, Total files/folders Archived $i" -BackgroundColor Green

Write-Host "Archival Processs Finished at $(Get-date), Please check the log file Archive $(($time).ToString("MM-dd-yyyy HH-MM")).Txt on Location $(Get-Item -Path ".\")" -BackgroundColor Green -ForegroundColor Blue

If(!(Test-Path -Path ".\Logs"))
     {
      New-Item -Path .\ -ItemType Directory -Name "Logs"
      Move-Item -Path ".\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt" -Destination ".\Logs"
      Write-Host "Log File .\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt has been moved to $(Get-Item -Path ".\Logs")" -BackgroundColor Green -ForegroundColor White 
     }
else{
      Move-Item -Path ".\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt" -Destination ".\Logs"
      Write-Host "Log File .\Archive $(($time).ToString("MM-dd-yyyy hh-mm tt")).Txt has been moved to $(Get-Item -Path ".\Logs")" -BackgroundColor Green -ForegroundColor White 
     }
}

else
{
Write-Host "No files or folder found for $Year" -BackgroundColor Green -ForegroundColor White
}

The script can be run in PowerShell Console Admin mode as below:

./Archive_files.ps1 -Year 2016

Optional Parameter $Destination Folder can be ignored as it is hard-coded in the script. You can pass it if the Destination folder is different.

Modify config files using PowerShell

Suppose you need to hide the password in your clear text connection strings at a particular back-up path or any other path in config files. This can be achieved using PowerShell with the following code.

$configFiles = Get-ChildItem "C:\Path\Backup" *.config -rec
foreach ($file in $configFiles)
{
    (Get-Content $file.PSPath) |
    Foreach-Object { $_ -replace "pwd=(\w+);", "pwd=****;" } |
    Set-Content $file.PSPath
}

Write-Verbose "Password changed!"

The above script will replace the string “pwd=(\w+);” with “pwd=****;” in the *.config files matching the regular expression.

I’m using the following parameters for the Get-ChildItem command:

-Path “C:\Path\Backup” 

-Include *.config for including config files

-rec for Recursion to get items in all child containers

Enable disable Windows Service and Scheduled Tasks with PowerShell

Often we may need to enable/disable Windows Services and Scheduled tasks on a Windows Server.

Below is the snippet to Enable Windows Service and Scheduled task with PowerShell:

$tasknames = "Test Task1","Test task2" $servicename = "Test Service1","Test Service2" foreach($task in $tasknames) {  ##disabling tasks on server  $taskstatus = Get-ScheduledTask -TaskName $task  if($taskstatus.State -eq "Disabled")   {    Enable-ScheduledTask -TaskName $task -Verbose   }   elseif($taskstatus.State -eq "Ready")   {   Write-Host "Task: $task already running"   }      } ##### Service checks and execution  foreach($service in $servicename)  {  $servicestatus = Get-Service $service  if($servicestatus.Status -eq "Stopped")   {    Start-Service $service  -Verbose    }    elseif($servicestatus.Status -eq "Started")    {    Write-host "Service: $service already Started"    }   } 

The below snippet shows how to disable the Scheduled tasks and Windows Service:

$tasknames = "Test Task1","Test task2" $servicename = "Test Service1","Test Service2" foreach($task in $tasknames) {  ##disabling tasks on server  $taskstatus = Get-ScheduledTask -TaskName $task  if($taskstatus.State -eq "Ready")   {    Disable-ScheduledTask -TaskName $task -Verbose   }   elseif($taskstatus.State -eq "Disabled")   {   Write-Host "Task: $task already disabled"   }      } ##### Service checks and execution  foreach($service in $servicename)  {  $servicestatus = Get-Service $service  if($servicestatus.Status -eq "Running")   {    Stop-Service $service -Force -Verbose    }    elseif($servicestatus.Status -eq "Stopped")    {    Write-host "Service: $service already stopped"    }   }  

You can save the above scripts in .ps1 format for PowerShell and call them e.g. using a .bat file.

Good old dir and copy commands

This is a true troubleshooting story!

Today, one of the users of my application wanted to know if we can find out the original title of a ticket created in the Application. Although, we do not store any such logs for modifications, while on the verge of giving up, I remembered we archive the e-mails received by the Windows Service application that processes the e-mails and archives them at a location on the Server on which it is installed. The e-mail Subject line is stored as the ticket title. These tickets which are created by the Windows Service can be viewed in the Web Application.

Since the folder with the archiving e-mails have a lot of ’em, simply doing a Windows Search is a time killer! So I remembered my way through the good old DOS commands.

Since the .eml file has the ticket number, I used the dir command to search for the e-mail name and copy command to copy it to another location quickly.

Firstly cd to the location where the archived e-mails are stored on the Server:

So e.g. if the ticket number is 123456

dir *123456* /s

The /s searches for the File name inside the two wildcard characters asterisk “*” with the dir command.

Then if the file name is e.g. TICKET123456 – abc.eml then use copy command as follows within the same directory:

copy "TICKET123456 - abc.eml" c:\Temp

So, with the e-mail having the original Subject Line preserved, I could share the original ticket title.