Sunday, 19 February 2017

Data replication With PowerShell Script

Data replication With PowerShell Script + Create log files for your scripts

The below article is focused on data replication from one source to destination - Not a folder synchronization script. It's always been a question to me how to replicate the content of one folder to another for a various purpose. This article also included logging option in Powershell, So we can follow, and see what is happening while the script is executing and copying the data or if the script runs into errors is very useful. Please follow the below article to know how can we achieve this using PowerShell.

Below cmdlets would be the input for the script. We need to define the source location, destination location and log file path for the script.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
###################################
# Source Location Details
#-------------------
$Source = "\\Server2012\ContentFolder"

# Destination Details
#-------------------
$Destination = "D:\ContentFolder"

# Log File Location
#-------------------
$Folderpath = "D:\FolderSync"

We have to declare few variable such as Log file name, error handling variables etc.

1
2
3
4
5
6
7
8
9
####################################
# Declaration of Variables
$Date1 = Get-Date -UFormat "%m-%d-%y"
$time = Get-Date -Format g
$Logname = "SyncLog.log"
$FilePath = "$Folderpath"+ "\$Logname"
$Errormap = $null
$errorcheck  = ''
$error.Clear()

The next part will be Create\Modify the Log file based on the availability of log and based on the size of the log - If the log file size is more than 1 MB (We can modify the size) log file will be replaced with a new log file.

 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
###################################

# Setting Log File

    If ((Test-Path -Path $Folderpath))
    {
            if ((Test-Path -Path $FilePath))
            {
 
                $filesize = Get-ChildItem $FilePath ; if ($filesize.Length -gt 100kb ) {Remove-Item $filesize.FullName -Force; New-Item $logfolder\$Logname -ItemType file -Force} 
                
                else {}
             }
            else
            {
                New-Item $FilePath -ItemType file -Force
            }
    }
    else
    {

        New-Item $Folderpath -Type directory -Force
        New-Item $FilePath -ItemType file -Force

    }

Checking the source location is accessible or not from the destination location - The script execution will exit in case if the source not accessible.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Connection to Source

If ((Test-Path -Path $Source))
    {
        Add-Content -Path $FilePath -Value "$time Connection to $Source established Successfully`n"  
    }
else
    {
        Add-Content -Path $FilePath -Value "$time Error : Connection to $Source Failed - Script execution stopped`n"
        exit
    }

The System.Security.Cryptography namespace provides cryptographic services, including secure encoding and decoding of data, as well as many other operations, such as hashing, random number generation, and message authentication. For more information. Refer the below link for more details.

https://msdn.microsoft.com/en-us/library/system.security.cryptography(v=vs.110).aspx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
##################################
function Get-FileMD5 {
    $error.Clear()
    Param([string]$file)
    $mode = [System.IO.FileMode]("open")
    $access = [System.IO.FileAccess]("Read")
    $md5 = New-Object System.Security.Cryptography.MD5CryptoServiceProvider
    $fs = New-Object System.IO.FileStream($file,$mode,$access)
    $Hash = $md5.ComputeHash($fs)
    $fs.Close()
    [string]$Hash = $Hash
    Return $Hash
}
Add-Content -Path $FilePath -Value "$time Data Replication Started`n"

Below part is replicating data and writing the respective logs into log file


 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
##################################

function Copy-LatestFile{
    $error.Clear()
    Param($File1,$File2,[switch]$whatif)
    $File1Date = get-Item $File1 | foreach-Object{$_.LastWriteTimeUTC}
    $File2Date = get-Item $File2 | foreach-Object{$_.LastWriteTimeUTC}
    if($File1Date -gt $File2Date)
    {
        #Write-Host "$File1 is Newer... Copying..." -ForegroundColor Green
        Add-Content -Path $FilePath -Value "$time $File1 is Newer. Copying...`n"
        if($whatif){Copy-Item -path $File1 -dest $File2 -force -whatif}
        else{Copy-Item -path $File1 -dest $File2 -force}
        $errormap = $error[0]
        if($error[0] -eq $null) {} else {Add-Content -Path $FilePath -Value "$time Error $errormap"; $errorcheck = "1"}
    }

}

# Getting Files/Folders from Source and Destination
$SrcEntries = Get-ChildItem $Source -Recurse
$DesEntries = Get-ChildItem $Destination -Recurse

# Parsing the folders and Files from Collections
$Srcfolders = $SrcEntries | Where-Object{$_.PSIsContainer}
$SrcFiles = $SrcEntries | Where-Object{!$_.PSIsContainer}
$Desfolders = $DesEntries | Where-Object{$_.PSIsContainer}
$DesFiles = $DesEntries | Where-Object{!$_.PSIsContainer}

foreach($folder in $Srcfolders)
{
    $error.Clear()
    $SrcFolderPath = $source -replace "\\","\\" -replace "\:","\:"
    $DesFolder = $folder.Fullname -replace $SrcFolderPath,$Destination
    if(!(test-path $DesFolder))
    {
        Add-Content -Path $FilePath -Value "$time Folder $DesFolder Missing. Creating it!`n"
        new-Item $DesFolder -type Directory | out-Null
        $errormap = $error[0]
        if($error[0] -eq $null) {} else {Add-Content -Path $FilePath -Value "$time Error  $errormap"; $errorcheck = "1"}
    }
}

foreach($entry in $SrcFiles)
{
    $error.Clear()
    $SrcFullname = $entry.fullname
    $SrcName = $entry.Name
    $SrcFilePath = $Source -replace "\\","\\" -replace "\:","\:"
    $DesFile = $SrcFullname -replace $SrcFilePath,$Destination
    if(test-Path $Desfile)
    {
        $SrcMD5 = Get-FileMD5 $SrcFullname
        $DesMD5 = Get-FileMD5 $DesFile
        If(Compare-Object $srcMD5 $desMD5)
        {

            Add-Content -Path $FilePath -Value "$time The Files MD5's are Different... Checking Write`n"
            Copy-LatestFile $SrcFullname $DesFile
            $errormap = $error[0]
            if($error[0] -eq $null) {} else {Add-Content -Path $FilePath -Value "$time Error  $errormap"; $errorcheck = "1"}
        }
    }
    else
    {
        $error.Clear()
        Add-Content -Path $FilePath -Value "$time $Desfile Missing... Copying from $SrcFullname`n"
        copy-Item -path $SrcFullName -dest $DesFile -force
        $errormap = $error[0]
        if($errormap -eq $null) {} else {Add-Content -Path $FilePath -Value "$time Error  $errormap"; $errorcheck = "1"}
    }
}

# Checking for Files that are in the Destinatino, but not in Source
foreach($entry in $DesFiles)
{
    $error.Clear()
    $DesFullname = $entry.fullname
    $DesName = $entry.Name
    $DesFilePath = $Destination -replace "\\","\\" -replace "\:","\:"
    $SrcFile = $DesFullname -replace $DesFilePath,$Source
    if(!(Test-Path $SrcFile))
    {

        Add-Content -Path $FilePath -Value "$time $SrcFile is deleted from Source... Deleting it from Destination`n"
        Remove-Item -path $DesFullname -Recurse -force
        $errormap = $error[0]
        if($error[0] -eq $null) {} else {Add-Content -Path $FilePath -Value "$time Error  $errormap"; $errorcheck = "1"}
    }
}
$error.Clear()
$Source = Get-ChildItem $Destination -Recurse
$dirs = $Source | Where { (gci $_.fullName).count -eq 0 } | select -expandproperty FullName
$dirs | Foreach-Object { Remove-Item $_ }
if ($errorcheck -ne '1') { Add-Content -Path $FilePath -Value "$time Data Replication completed successfully`n"}
else {Add-Content -Path $FilePath -Value "$time Data Replication completed with some error - Please check the log for details`n"}

###################################

Combine all the script together and run.

Click here to download the script from Technet

Tuesday, 16 August 2016

File Search - Using Powershell

How to search a file located in Local\Remote System using PowerShell

As you can see,  this is about a simple WMI query + Powershell for searching a file with the file extension. In this script we are connecting to the wmi service of a Local\Remote computer and executing the query to get the file details. After getting the details we setup a foreach loop for the collection to extract the details.

Below is the two important WMI query which we are executing through powershell. I am searching a files with bmp extension.

1
2
$SearchObject = Get-Wmiobject -namespace "root\CIMV2" -computername $System -Query "Select * from CIM_DataFile Where Extension = 'bmp'"
$query = "ASSOCIATORS OF {Win32_LogicalFileSecuritySetting='" + $filepath + "'} WHERE AssocClass=Win32_LogicalFileOwner ResultRole=Owner"

Note : As per the below script, we are running the query against entire file system which will take long time to get the details based on the number of files present on the targeted machine. Also you can search multiple extension as well by adding AND or OR  operator.

Below is the information which we are additionally getting from the script.
1
2
3
4
5
6
7
8
MACHINE_NAME
PING_STATUS
FILE_PATH
USER_NAME
FILE_SIZE
LAST_MODIFIED
SYSTEM_MAKE
SYSTEM_MODEL

Below is script which is created based on a  project requirement. In this script I have copied the system hostnames in a .TXT file for searching from multiple system. ( This is required only if you are searching the files in multiple systems.)

 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
$details = @()
$PCList = Get-Content C:\temp\PC.TXT

foreach ($System in $PCList)
{
$pstsize = ""
$modified = ""
$Make = ""
$model = ""
$SearchObject = $null

If(!(Test-Connection -Cn $System -BufferSize 16 -Count 1 -ea 0 -quiet))
{
    $Result = @{
    MACHINE_NAME     = "$System"
    PING_STATUS      = "FAILED"
    FILE_PATH        = "N/A"
    USER_NAME        = "N/A"
    FILE_SIZE         = "N/A"
    LAST_MODIFIED    = "N/A"
    SYSTEM_MAKE = "N/A"
    SYSTEM_MODEL = "N/A"
                        }
    $Details += New-Object PSObject -Property $Result
}
Else
{

    $MakeDetails = Get-WmiObject -Class win32_computersystem -ComputerName $System
    $Make = $MakeDetails.Manufacturer
    $Model = $MakeDetails.Model
      
    $SearchObject = Get-Wmiobject -namespace "root\CIMV2" -computername $System -Query "Select * from CIM_DataFile Where Extension = 'bmp'"
    #$SearchObject = Get-WmiObject CIM_Datafile -ComputerName $System | Where-Object {$_.Extension -eq 'txt'}

if($SearchObject)
{

    foreach ($ObjectFile in $SearchObject)
    {

        $filepath = $ObjectFile.Drive + $ObjectFile.Path + $ObjectFile.FileName + "." + $ObjectFile.Extension
        $query = "ASSOCIATORS OF {Win32_LogicalFileSecuritySetting='" + $filepath + "'} WHERE AssocClass=Win32_LogicalFileOwner ResultRole=Owner"
        $FileOwner = Get-Wmiobject -namespace "root\CIMV2" -computername $System -Query $query

        $FileOwnerName = $FileOwner.AccountName

        $output = $System + "," + $filepath + "," + $filepath + "," + $ObjectFile.FileSize/1KB + "," + $ObjectFile.LastModified
        $modified = $ObjectFile.LastModified
        $pstsize  =    $ObjectFile.FileSize/1KB

        $Result = @{
        MACHINE_NAME     = "$System"
        PING_STATUS      = "SUCCESS"
        FILE_PATH        = "$filepath"
        USER_NAME        = "$FileOwnerName"
        FILE_SIZE         = "$pstsize"
        LAST_MODIFIED    = "$modified"
        SYSTEM_MAKE      = "$Make"
        SYSTEM_MODEL     = "$model"
                            }
        $Details += New-Object PSObject -Property $Result
        }
    }
    else
    {    
        $Result = @{
        MACHINE_NAME     = "$System"
        PING_STATUS      = "SUCCESS"
        FILE_PATH        = "NO PST FILE FOUND"
        USER_NAME        = "N/A"
        FILE_SIZE         = "N/A"
        LAST_MODIFIED    = "N/A"
        SYSTEM_MAKE = "$Make"
        SYSTEM_MODEL = "$model"
                            }
        $Details += New-Object PSObject -Property $Result
    }
}
}

$pathofcsv = "C:\TEMP\" + "FILE_DETAILS" + "$date" + ".csv"
$Details | export-csv -Path $pathofcsv -Append -NoTypeInformation

The output of the script will be as given below.



Saturday, 6 August 2016

WINDOWS 10 IN-PLACE UPGRADE

WINDOWS 10 IN-PLACE UPGRADE USING SYSTEM CENTER CONFIGURATION MANAGER 2012 R2-SP1

1.1 Prerequisites

·         A System Center 2012 R2 Configuration Manager site is already installed and configured.
·         Configuration Manager Clients are deployed and running at least Windows 7 OS.
·         Windows 10 Enterprise (32 and 64 Bit) media (ISO file should be extracted as pictured below).

·         








Download the Task sequence from the below URL and extract it to a network share.
It should look like below.
Windows-vNextUpgradeExport.zip
Windows-vNextUpgradeExport_files
o    Windows vNext Upgrade Media (empty)
o    Windows vNext Upgrade Script
https://blogs.technet.microsoft.com/enterprisemobility/2015/06/16/revised-content-for-the-windows-10-in-place-upgrade-via-task-sequence-for-configuration-manager/

1.2 Preparation of Task Sequence.

Extract the downloaded Task Sequence to our deployment root (Where we are placing all the software binaries required for SCCM)
1.      Copy the contents of the Windows 10 Enterprise media into the “Windows vNext Upgrade Media” subfolder. An Upgrade package automatically creates during the Import TS process explained below (Point-3).










2.    “Windows vNext Upgrade Scripts” folder contains the required PowerShell script during the Upgrade process. An Upgrade Script Package automatically creates during the Import TS process.






3.      Open the Configuration Manager Console, switch to the Software Library workspace, expand Operating Systems, right-click Task Sequences, and select Import Task Sequence
Note:
Downloaded Task Sequence cannot be imported twice on our SCCM console. We have to Right Click on the Imported Task Sequence and Copy to have another Task Sequence – We may need two TS for importing multiple Built/Version for Windows10 Operating System. Process is explained in the below article.

1.3 Task Sequence Creation

1)  Open the Configuration Manager Console, switch to the Software Library workspace, expand Operating Systems, right-click Task Sequences, and select Import Task Sequence











2)    Browse or Enter the UNC path of vNextUpgradeExport.zip










3) Select Windows-vNextUpgradeExport.zip and click Next











4)  The File Content page will display the following objects with the action, Create New (Action will be Ignore Duplicate in case if we are tried to Import the same TS again.)

o   Windows vNext Upgrade Media (Package)
o   Windows vNext Upgrade Scripts (Package)
o   Windows vNext Upgrade (Task Sequence Package)













5)    Click on close button from summary page.











6)    Windows10 Upgrade Task Sequence is ready for the deployment. Distribute the Task Sequence to appropriate distribution point – Below is the reference package which is related to the Task sequence.

o   Windows vNext Upgrade Media
o   Windows vNext Upgrade Scripts

7)    Make sure that these two packages are distributed properly before starting Upgrade.












1.4 Task Sequence Modification.

Below are the Important steps involved in the task sequence, we can Right Click on the Task sequence to edit it.

Check Readiness:

These are the minimum system requirements for Windows but can be adjusted as necessary for your environment. Windows Upgradation will fail on Check Reediness step in case the systems are not met as per the configuration.

1.                  Minimum Memory : 2048 in MB
2.                  Minimum Processor : 1024 in MHz
3.                  Free Disk space in System Directory : 5120 in MB
4.                  Current OS to be referred is : CLIENT (Client Operating Systems)














Note:

SMSTS.Log will provide all the information about Task Sequence and we can refer this log for troubleshooting purpose.

PreSetup:  This runs a corresponding Windows PowerShell script (PreSetup.ps1) to perform a variety of necessary actions prior to running Windows Setup.

Stage Content:  A simple copy of some scripts to a known, local staging directory to be referenced elsewhere in the process

Upgrade Windows:

This is where setup is actually run from the media package to automatically upgrade Windows.

As mentioned earlier in this article, downloaded Task Sequence cannot be imported twice on our SCCM console. We have to follow the below steps for creating another Task sequence from the existing one.
           
1.      Create a Package without program using Windows-10 source file and distribute the package to required distribution point.
2.      Create a New Copy of Task Sequence from our Imported Task sequence (Right Click on the TS and Copy it).
3.      Edit the New Task Sequence and click on Upgrade Windows Step.
4.      Click on the Browse Button and point the newly created Windows-10 Package (Highlighted in red on the above picture)
















Restart:  The task sequence is configured such that this step is only processed if the Upgrade Windows step succeeds. These three steps (Upgrade Windows Recover from Setup Failure, and Restart Computer) need to be kept together for the process to work properly.

·     We can Modify the User Notification on Restart Window
·     Message Delay can be increased as per our requirement. Default time-out will be 30 second.

Post-Processing:   This group is processed when setup successfully completes. Additional steps can be added to this group as needed, such as Install Applications, Run PowerShell and Run Command Line etc.

Rollback: This group is only processed in a rollback scenario. Additional steps can be added to this group as needed, for example to copy logs to a network share. The Trigger Rollback Failure step should be the last one in that group to purposefully fail the task sequence for proper status monitoring.

Saturday, 21 November 2015

Exchange Mailbox migration using PowerShell


Introduction : This document is just about the exchange mailbox migration --- I am just covering how can we migrate a single & multiple user's mailbox - Moving mailbox from one server to another server in the same forest is very simple process which we can  achieve either through powershell scripts or from the exchange console...

First we will start with mailbox movement for single user

How to get user mailbox details ?

001
Get-mailbox -Identity viviana









How to move mailbox ?

Refer the below important parameters :

TargetDatabase : The TargetDatabase parameter specifies whether to return all mailboxes that are being moved to the specified target database.
SuspendWhenReadyToComplete : The SuspendWhenReadytoComplete parameter specifies whether to return mailboxes that have been moved with the New-MoveRequest command and its SuspendWhenReadyToComplete
BadItemLimit : The BadItemLimit parameter specifies the maximum number of bad items that are allowed before the request fails. A bad item is a corrupt item in the source mailbox that can't be copied to the target mailbox. Also included in the bad item limit are missing items. Missing items are items in the source mailbox that can't be found in the target mailbox when the request is ready to complete.

Before move request, you can validate if you want to know what is actually going to happen by using -whatif cmdlet -- & use the above important parameters which may required in your production environment.


001
New-MoveRequest –identity viviana –TargetDatabase MDB02 -whatif










I just used -whatif for validating what exactly going to happen --- Rerun the same command by removing -whatif  for mailbox movement

001
New-MoveRequest –identity viviana –TargetDatabase MDB02











We can suspend or remove the move request - If you suspend the move request, that can be resumed, but removed move request can't be resumed back.


001
002
Suspend-MoveRequest -Identity viviana
Remove-MoveRequest -Identity viviana

How to check the status of move request ?

001
002
003
004
Get-MoveRequest
#----- We can also select particular properties as showing below
Get-MoveRequest | select DisplayName,Alias,Status,TargetDatabase,percentcomplete
Get-MoveRequestStatistics -Identity viviana










If you used SuspendWhenReadytoComplete parameter, you need to resume the move request in order to complete the move request - See the below command

001
Resume-MoveRequest -Identity viviana

Mailbox movement for Multiple Users

As per the above examples, we are just migrating a single user - But in production we need to migrate thousands of users -
All we need to do is -  Create an Input file which contains users & database details (Add database column only if we are moving users to multiple database for load balancing purpose, otherwise you can directly mention the database name - like moving single user)

Batch Name : Batch name is one of the important parameter in new move request in case if we are moving multiple mailboxes. The Batchname parameter specifies a descriptive name for moving a batch of mailboxes

I am taking input from a .CSV File --- As I am moving mailboxes to different database, included Database column as well in my input file.
Below is my Input File and saved it under C:\Project\Input.csv










As per the below script, we are just directly moving mailboxes - We can use the below important parameters as well.
-SuspendWhenReadyToComplete
-BadItemLimit

001
002
003
004
005
$csvinput = Import-Csv -Path 'C:\Project\Input.csv'
Foreach ($csv in $csvinput)
{
New-MoveRequest –identity ($csv.alias) –TargetDatabase ($csv.database) -BatchName "TESTMove01"
}

If we are moving mailboxes to the same Database, mention only Alias in CSV file and use the below script -- You need to type the database name manually in this case.
Note : We can use notepad as well if we are importing only user details - Replace Import-Csv with Get-Content cmdlet

001
002
003
004
005
$csvinput = Import-Csv -Path 'C:\Project\Input.csv'
Foreach ($csv in $csvinput)
{
New-MoveRequest –identity ($csv.alias) –TargetDatabase "MDB01" -BatchName "TESTMove01"
}

Move request status


1
2
3
4
5
6
7
8
9
Get-MoveRequest -BatchName "MDB01"
#-- Grid View with selected properties
Get-MoveRequest -BatchName "MDB01" | select DisplayName,Alias,Status,TargetDatabase,percentcomplete | Out-GridView

# we can use the below cmdlet as well

Get-MoveRequestStatistics -BatchName "MDB01" | select DisplayName,Alias,Status,TargetDatabase,percentcomplete | Out-GridView
<# Get-MoveRequestStatistics ::: Some of the failure messages that are returned by this cmdlet are temporary and don't indicate that a request -
has actually failed. If the Status value is Queued or InProgress,then the request is proceeding normally #>

Move request status summary


001
Get-MoveRequest -BatchName "MDB01" | group status -noelement

Export your status report


001
002
Get-MoveRequest -BatchName "MDB01" |select DisplayName,Alias,Status,TargetDatabase,percentcomplete |
Export-Csv -Path "C:\Report\Outputt.csv" -NoTypeInformation


Reference Page - Technet
Download Scrips                                          

                                                     Happy Migration   :)