PS ISE – Measure Script speed

Quick and easy way to measure execution time on a script, add the following to Microsoft.PowerShellISE_profile.ps1

Function Run-VerboseMeasured {
    PARAM (
        $scriptFullPath
    )
    Write-Host "INFO: Running:  . '$($scriptFullPath)' -Verbose" -ForegroundColor Green
    
    $m = Measure-Command {Invoke-Expression -Command ". '$($scriptFullPath)' -Verbose"}

    if( $m.TotalSeconds -gt 10) {
        Write-Host "INFO: Script took $($m.TotalSeconds) sec to run" -ForegroundColor Green
    } else {
        Write-Host "INFO: Script took $($m.TotalMilliseconds) ms to run" -ForegroundColor Green
    }

}

$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('Run with -Verbose (measured)', { Run-VerboseMeasured $psISE.CurrentFile.FullPath }, 'Ctrl+F5') | Out-Null

Then, restart ISE and now you can Add-ons you will have a new menu item. ๐Ÿ™‚

Advertisements

Silent patching and controlled reboots

Patching silently and installing applications without any user interaction… This is really hard to do and it’s really hard to get compliant machines if you do not force a reboot every now and then.
Then, if you force the user to reboot with a short timeout they will not be to happy.
So… to solve this I took some inspiration from a couple of blogposts some scripts and then some of my knowledge and put together a recipe that I think work’s really nice. ๐Ÿ™‚

All in all, this results in a nice popup (only when a reboot is needed) that gives the user a good amount of time to decide when to reboot.
With this in place you can push out updates and applications silently and then just sit back and wait for the users to reboot when they want to.

So, this is what you need to do…

Copy the following files to %ProgramFiles%RebootIfNeeded

Create a Scheduled Task that runs once or twice every day (I have it set at 08:00 and 13:00 every weekday), and on that task create an with the following configuration:
Program: %ProgramFiles%RebootIfNeededhstart64.exe
Arguments:

/NOCONSOLE /WAIT ""%SystemRoot%system32WindowsPowerShellv1.0powershell.exe" -NoLogo -NoProfile -NonInteractive -File "%ProgramFiles%RebootIfNeededRebootIfNeeded.ps1""

(Tip: If using GPP, set item level targeting to check that the PS1-file exist before creating the task)

The script will check for pending reboots and if the computer havn’t rebooted for XX days.
You can add support for more languages, just extend the hashtable $restartDescriptions

[CmdletBinding()]
PARAM (
    $maxBootAgeDays = 35,
    $restartTimeOut = (9 * 60), # 9 hours
    $restartMaxPostpone = (48 * 60), # 48 hours
    $restartDescriptions = @{
        "en-US" = "Your computer needs to restart to receive the latest updates.";
        "sv-SE" = "Din dator behรถver startas om fรถr att fรฅ de senaste uppdateringarna.";
    },
    $defaultLanguage = "en-US"
)


Function Get-PendingReboot {
	# Local HKLM
	$HKLM = [UInt32] "0x80000002"
	$wmiRegistry = [WMIClass] "\.rootdefault:StdRegProv"

	#Default
    $PendingReboot = $false

	# CBS - Reboot Required ?
	$RegSubKeysCBS = $wmiRegistry.EnumKey($HKLM,"SOFTWAREMicrosoftWindowsCurrentVersionComponent Based Servicing")
	if ($RegSubKeysCBS.sNames -contains "RebootPending") {
        Write-Verbose "Component Based Servicing have a reboot pending"
		$PendingReboot = $true
    }
							
	# Windows Update - Reboot Required?
	$RegistryWUAU = $wmiRegistry.EnumKey($HKLM,"SOFTWAREMicrosoftWindowsCurrentVersionWindowsUpdateAuto Update")
	if ($RegistryWUAU.sNames -contains "RebootRequired") {
        Write-Verbose "Windows Update have a reboot required"
		$PendingReboot = $true
    }

	## Pending FileRenameOperations ?
	$RegSubKeySM = $wmiRegistry.GetMultiStringValue($HKLM,"SYSTEMCurrentControlSetControlSession Manager","PendingFileRenameOperations")
	If ($RegSubKeySM.sValue) {
        $RegSubKeySM.sValue | ForEach-Object { 
        	If ($_.Trim() -ne '') {
        		Write-Verbose "Pending FileRename operation: $($_)"
        	}
        }
		$PendingReboot = $true
    }

	# ConfigMgr - Pending reboot ?
	TRY {
	    $CCMClientSDK = Invoke-WmiMethod -NameSpace "ROOTccmClientSDK" -Class "CCM_ClientUtilities" -Name "DetermineIfRebootPending" -ErrorAction Stop

		If ($CCMClientSDK.IsHardRebootPending -or $CCMClientSDK.RebootPending) {
            Write-Verbose "ConfigMgr have reboot pending"
            $PendingReboot = $true
        }
	} CATCH {
        Write-Verbose "Cant talk to ConfigMgr Agent"
    }

    Write-Verbose "Pending reboot: $($PendingReboot)"
    Return $PendingReboot
}

Function Check-OldBootAge {
    PARAM (
        $maxAgeDays = 35
    )

    $BootTime = Get-WmiObject  Win32_Operatingsystem
    [Int]$days = (New-TimeSpan -Start $boottime.ConvertToDateTime($boottime.LastBootUpTime) -End (Get-Date)).TotalDays

    if ($days -ge $maxAgeDays) {
        Write-Verbose "Boot age is $($days) days (more than $($maxBootAgeDays)), reboot required"
        Return $true
    } else {
        Write-Verbose "Boot age is $($days) days (less than $($maxBootAgeDays))"
        Return $false
    }

    Return $days
}

Function Get-UserLanguage {
    Return [String] ([System.Threading.Thread]::CurrentThread).CurrentUICulture.Name
}

# ------------------------------------------------------------------------------------------------------------
# Main script

if ( (Get-WmiObject -Query "SELECT ProductType FROM Win32_OperatingSystem").ProductType -eq 1) {

    If ( (Get-Process "ShutdownTool" -ErrorAction SilentlyContinue) ) {
        Write-Host "Already running ShutdownTool"
    } else {
        If ((Check-OldBootAge -maxAgeDays $maxBootAgeDays) -or (Get-PendingReboot)) {
            Write-Host "Reboot is required, calling restart utility"

            $userLanguage = Get-UserLanguage
            Write-Verbose "Language: $($userLanguage)"

            # Find description
            $Description = $restartDescriptions[$userLanguage]
            if ($Description -eq $null) {
                $Description = $restartDescriptions[$defaultLanguage]
            }

            $timeOutSeconds = ($restartTimeOut*60) - 1

            Write-Verbose "Restart timeout: $($timeOutSeconds) seconds"
            Write-Verbose "Max postpone: $($restartMaxPostpone) minutes"
            Write-Verbose "Description: $($Description)"


            If ((Test-Path ".ShutdownTool.exe") -eq $false) {
                Throw "Cant find ShutdownTool.exe"
            } else {
                Write-Verbose "Calling restart with ShutdownTool"
                .ShutdownTool.exe /g:$userLanguage /d:"$Description" /t:$timeOutSeconds /m:$restartMaxPostpone /r /c
            }

            # /g - Language
            # /d - description
            # /t - countdown in sec
            # /m - max postpone in min
            # /r - reboot instead of shutdown
            # /c - force & remove abort-btn
        }
    }

} else {
    Write-Verbose "Not a client OS"
}
# Done!

Merry Instagram Christmas

OK, this post might be a bit late… But if you have a Instagramoholic friend that you don’t know what to give for christmas, this might be the thing. ๐Ÿ™‚

Running this will output an image of all (Well, a big chunk at least) of the users Instagram images in one single image, then just order a nice print and you are set for christmas.

Will look something like this if you use my account
Example

PARAM (
    $UserName = "rirofal",
    $DownloadPath = "C:ScriptspsInstaMozDL",
    $outputImageName = "C:Scriptsmozaic.jpg",
    $instaImageSize = 250,
    $maxNofImages = 1600
)

if(!(Test-Path $DownloadPath)) { 
    Throw "Cant access $($DownloadPath)"
}

$JsonData = Invoke-WebRequest "http://instagram.com/$($UserName)/media" | ConvertFrom-Json

$imgNo = 0
while ( $JsonData.more_available -eq $true ) {
    foreach ($item in $JsonData.items) {
        $ImageURL = $item.images.standard_resolution.url
        $ImageDownloadPath = Join-Path -Path $DownloadPath -ChildPath $ImageURL.Split('/')[-1]

        if( !(Test-Path $ImageDownloadPath) ){
            Write-Host "Downloading $($ImageDownloadPath)"
            Invoke-WebRequest $ImageURL -OutFile $ImageDownloadPath
        } else {
            Write-Host "Allready downloaded $($ImageDownloadPath)"
        }
        $imgNo ++
    }

    if ($imgNo -gt $maxNofImages) {
        Write-Host "Reached max of $($maxNofImages)"
        Break
    }

    $LastID = ($JsonData.items | Select -Last 1).id
    $JsonData = Invoke-WebRequest "http://instagram.com/$($UserName)/media?max_id=$LastID" | ConvertFrom-Json
}

# Read local files and calulate output image size
$localFiles = Get-ChildItem -Path $DownloadPath
Write-Host "Downloaded $($localFiles.Count) files"

$sqrtCount = [math]::Sqrt($localFiles.Count)

Write-Host "Square is $($sqrtCount) images"

$absSqrtCount = [math]::floor($sqrtCount)
Write-Host "Rounded down number is $([int] $absSqrtCount)"

[int] $outputImageSize = $absSqrtCount * $instaImageSize
Write-Host "Resulting ImageSize will be $($outputImageSize) px"


# Start to create output image
[Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null

$outFile = New-Object System.Drawing.Bitmap( $outputImageSize,  $outputImageSize )

Write-Host "Selecting $($absSqrtCount * $absSqrtCount) random local files"
$localFiles = $localFiles | Get-Random -Count ($absSqrtCount * $absSqrtCount)

$x = 0
$y = 0

$outImage = [System.Drawing.Graphics]::FromImage($outFile)
$outImage.SmoothingMode = "AntiAlias"

foreach ($localImage in $localFiles) {
    Write-Host "Adding $($localImage.FullName) to output image at X: $($x), Y: $($y)"

    $srcImg = [System.Drawing.Image]::FromFile($localImage.FullName)
    $outImage.DrawImage($srcImg, $x, $y, $instaImageSize, $instaImageSize)

    $x = $x + $instaImageSize
    if ($x -gt ($outputImageSize - $instaImageSize) ) {
        $y = $y + $instaImageSize
        $x = 0
    }
}

Write-Host "Saving JPEG to $($outputImageName)"
$outFile.save($outputImageName, [System.Drawing.Imaging.ImageFormat]::Jpeg)
$outFile.Dispose()

A big thanks to https://github.com/baurmatt/instagram-image-dumper for Instagram downloader code.

Migrate printers to new server

If you move printers from one server to another the users needs to reconnect all printers…

And… of course it’s easy to do with a small Powershell script. ๐Ÿ™‚

Function Migrate-Printer {
    PARAM (
        [string] $ShareName,
        [string] $oldServer,
        [string] $newServer
    )

    $currentPrinter = Get-WmiObject -Query "SELECT * FROM Win32_Printer WHERE Network=True AND ShareName = '$($ShareName)' AND SystemName = '\\$($oldServer)'"

    if ($currentPrinter -eq $null) {
        Write-Verbose "Cant find \$($oldServer)$($ShareName)"
    } else {
        Write-Verbose "Migrating printer $($ShareName) from $($oldServer) to $($newServer)"

        $net = New-Object -com WScript.Network

        Write-Verbose "Adding printer \$($newServer)$($ShareName)"
        $net.AddWindowsPrinterConnection("\$($newServer)$($ShareName)")

        Write-Verbose "Removing printer \$($newServer)$($ShareName)"
        $net.RemovePrinterConnection("\$($oldServer)$($ShareName)")
    

        if ($currentPrinter.Default -eq "True") {
            Write-Verbose "Setting default to \$($newServer)$($ShareName)"
            $net.SetDefaultPrinter("\$($newServer)$($ShareName)")
        }
    }

}

Example usage

Migrate-Printer -ShareName "PRINTER001" -oldServer "OLDSERVER" -newServer "NEWSERVER"

Copy drivers from one boot image to another

When you have a new ConfigMgr boot image ready but are missing some drivers from an old one… might be hard to find them in a larger structure.

… Powershell to the rescue! ๐Ÿ™‚

Function Copy-BootImageDrivers {
    PARAM (
        $from, $to
    )

    $boot = Get-CMBootImage -ID $to

    (Get-CMBootImage -Id $from).ReferencedDrivers | ForEach-Object {
        Write-Verbose "Copying $($_.Id) to $($to)"
        Set-CMDriver -Id $_.Id -AddBootImagePackage $boot -UpdateDistributionPointsforBootImagePackage $false
    }

}

#Example use
Copy-BootImageDrivers -from "ABC00123" -to "ABC00456"

Find undefiened networks in netlogon.log

To find undefined networks in your AD you can parse the netlgon.log files on the domain controllers.
(This script will gather all errors you can of add some “If ($_.Error -like ‘NO_CLIENT_SITE*’) …” if you only want that kind of error.)

$domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
Write-Host "Using domain $($domain.Name)"

# Copy files to %Temp%
($domain).DomainControllers | Foreach-Object {
    $netlogonSource = "\$($_.Name)Admin$debugnetlogon.log"
    $netlogonTarget = (Join-Path (Get-Item Env:Temp).Value "netlogon-$($_.Name.Replace($domain.name, ''))log")


    Write-Host "Copy from $($netlogonSource) to $($netlogonTarget)"
    Copy-Item -Path $netlogonSource -Destination $netlogonTarget -Force
}

# Process local files to hashtable
$networks = @{}
Get-ChildItem -Path "$((Get-Item Env:Temp).Value)netlogon*.log" | Sort-Object FullName | ForEach-Object {
    Write-Host "Processing $($_.FullName) " -NoNewline

    Import-Csv $_.FullName -Delimiter ' ' -Header Date, Time, Domain, Error, Name, IPAddress | ForEach-Object {
        if (! $networks.ContainsKey($_.IPAddress)) {
            # IP not in list, adding
            Write-Host "." -NoNewline -ForegroundColor Green
            $networks.Add($_.IPAddress, "$($_.Domain)$($_.Name) $($_.Error) $($_.Date) $($_.Time)")
        } else {
            # Allready there
            Write-Host "." -NoNewline -ForegroundColor Red
        }
    }
    Write-Host ""
}

# Export to new CSV
$outFile = "$((Get-Item Env:Temp).Value)netlogonData.csv"
Write-Host "Export data to $($outFile)"
$networks.GetEnumerator() | Select-Object Name, Value | Export-Csv -Path $outFile -Force 

# Remove temporary netlogon-files
Get-ChildItem -Path "$((Get-Item Env:Temp).Value)netlogon*.log" | Remove-Item -Force

FYI:
Green dot – Unique IP found
Red dot – Duplicate (will not be added to list)

And in the end there will be a CSV-file in %Temp% that you can use.

๐Ÿ™‚

Remove old logfiles

Want to clean out old logfiles from IIS (or other products)?

PARAM (
    [int] $daysBack = 7,
    [string] $logPath = "C:InetpubLogsLogFiles"
)

Get-ChildItem $logPath -Recurse -Include *.LOG | Where-Object {$_.CreationTime -lt (Get-Date).AddDays(0-$daysBack)} | ForEach-Object {
	Write-Host "Processing: " -ForegroundColor Yellow -NoNewline
	Write-Host $_.FullName -ForegroundColor White -NoNewline
	
	$span = New-TimeSpan $_.CreationTime $(get-date)
	Write-Host " $($span.Days) days old" -ForegroundColor Yellow -NoNewline

	TRY {
        Remove-Item $_.FullName -Force -ErrorAction Stop
        Write-Host " [Deleted]" -ForegroundColor Green
    }

    CATCH {
        Write-Host " [Can't delete]" -ForegroundColor Red
    }
}