Category: PowerShell

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. 🙂

Written by Comments Off on PS ISE – Measure Script speed Posted in PowerShell

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%\RebootIfNeeded\hstart64.exe
Arguments:

/NOCONSOLE /WAIT ""%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -NonInteractive -File "%ProgramFiles%\RebootIfNeeded\RebootIfNeeded.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] "\\.\root\default:StdRegProv"

	#Default
    $PendingReboot = $false

	# CBS - Reboot Required ?
	$RegSubKeysCBS = $wmiRegistry.EnumKey($HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Component 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,"SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\")
	if ($RegistryWUAU.sNames -contains "RebootRequired") {
        Write-Verbose "Windows Update have a reboot required"
		$PendingReboot = $true
    }

	## Pending FileRenameOperations ?
	$RegSubKeySM = $wmiRegistry.GetMultiStringValue($HKLM,"SYSTEM\CurrentControlSet\Control\Session 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 "ROOT\ccm\ClientSDK" -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:\Scripts\psInstaMoz\DL",
    $outputImageName = "C:\Scripts\mozaic.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"
Written by Comments Off on Migrate printers to new server Posted in PowerShell

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$\debug\netlogon.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:\Inetpub\Logs\LogFiles"
)

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

Get Powershell ISE to run scripts with -Verbose flag

Missing an easy way to run your scripts from ISE with the -Verbose or -Debug flag?

Easy to add… Open up your Microsoft.PowerShellISE_profile.ps1 file and add the following lines:

$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('Run with -Verbose', { Invoke-Expression -Command ". '$($psISE.CurrentFile.FullPath)' -Verbose" }, 'Ctrl+F5') | Out-Null
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('Run with -Debug',   { Invoke-Expression -Command ". '$($psISE.CurrentFile.FullPath)' -Debug" }, 'Ctrl+F6') | Out-Null

Now when you restart ISE you should see the options under the Add-Ons menu.

Create Site Roles Collections

Quick and easy way to create device collections based on site roles:

$wmiParams = @{
    "Namespace" = "root\SMS\site_ABC";
    "Query" = "SELECT RoleName FROM SMS_SystemResourceList"
}

Get-wmiobject @wmiParams  | Group-Object RoleName | ForEach-Object {
    Write-Verbose "Creating collection for role $($_.Name)"
    $newCollection = New-CMDeviceCollection -Name "ConfigMgr-Role $($_.Name)" -LimitingCollectionID "SMS00001" -RefreshType Periodic -RefreshSchedule (New-CMSchedule -RecurInterval Days -RecurCount 4 -Start (Get-Date))
    $query = "SELECT * FROM SMS_R_System WHERE SMS_R_System.SystemRoles = '$($_.Name)'"
    Add-CMDeviceCollectionQueryMembershipRule -CollectionId $newCollection.CollectionID -QueryExpression $query -RuleName "CMRole-$($_.Name)"
}

You will end up with collections like:
– ConfigMgr-Role SMS Distribution Point
– ConfigMgr-Role SMS Device Management Point
– ConfigMgr-Role … and so on …

Get filename in ConfigMgr 2012 ContentLib

In ConfigMgr 2007 it was kind of convinient to be able to edit files directly on a DP, in ConfigMgr 2012 that isn’t to easy if you are using Content Library.

There are a few ways to find out where the files are actually stored, here is one way:

(Will give you the path to somefile.xml in package ABC01234)

PARAM (
	$siteCode = "ABC",
	$FileName = "somefile.xml",
	$PackageID = "ABC01234",
	$ContentLib = "D:\SCCMContentLib"
)
Import-Module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + '\ConfigurationManager.psd1')
Set-Location "$($siteCode):"


$package = Get-CMPackage -ID $PackageID
$hash = (Get-Content "$($ContentLib)\DataLib\$($package.PackageID).$($package.StoredPkgVersion)\$($FileName).INI" | Where-Object { $_ -like "Hash=*" }).Replace("Hash=", "")
$storePath =  Join-Path "$($ContentLib)\FileLib" "$($hash.Substring(0,4))\$($hash)"

$sourcePath = Join-Path $package.PkgSourcePath $FileName

Write-Host "Source: $($sourcePath)"
Write-Host "Store : $($storePath)"

Will result in something like this:

Source: \\server\share\path\to\package source\somefile.xml
Store : D:\SCCMContentLib\FileLib\A123\A1234567890ABCDEF1234567890ABCDEF

And with this you can edit the XML-file directly on the store instead of editing source, update DPs, wait for processing, wait some more…

… but, I’m not saying that I recommend anyone to edit files directly in the store. 😉

SwitchKing PoSH

Changelog

  • 2013-10-06 (Rikard Ronnkvist) First release
  • 2013-10-06 (Rikard Ronnkvist) Added info on relative commands.

Requirements

SwitchKing.psm1

#requires -version 3
<#
Version history:
    2013-10-06   Rikard Ronnkvist   First release.
    2013-10-06   Rikard Ronnkvist   Added info on relative commands.

Usage:
   PS:>Import-Module SwitchKing
   PS:>Get-Command -Module SwitchKing
   PS:>Get-Help Get-SwitchKingInfo -Examples
   PS:>Get-Help Set-SwitchKingDevice -Examples

Not sure where to save the file?
   PS:>Write-Host (Join-Path (Split-Path -Path $profile -Parent) "Modules\SwitchKing\SwitchKing.psm1")
#>
[cmdletbinding()]
PARAM ()

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#region Helpers
Function Get-SKHelperValidateMinutesParam {
<#
.SYNOPSIS
    Helper to validate input in functions
#>
    PARAM (
        [Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )] $Minutes = 60,
        [Parameter(Mandatory=$false)] [switch] $GetParams
    )

    $validParams = @(
        [int] (New-TimeSpan (Get-Date) (Get-Date).AddHours(1)).TotalMinutes,
        [int] (New-TimeSpan (Get-Date) (Get-Date).AddHours(3)).TotalMinutes,
        [int] (New-TimeSpan (Get-Date) (Get-Date).AddHours(6)).TotalMinutes,
        [int] (New-TimeSpan (Get-Date) (Get-Date).AddHours(12)).TotalMinutes,
        [int] (New-TimeSpan (Get-Date) (Get-Date).AddHours(24)).TotalMinutes,
        [int] (New-TimeSpan (Get-Date) (Get-Date).AddDays(2)).TotalMinutes,
        [int] (New-TimeSpan (Get-Date) (Get-Date).AddDays(7)).TotalMinutes,
        [int] (New-TimeSpan (Get-Date) (Get-Date).AddDays(30)).TotalMinutes,
        [int] (New-TimeSpan (Get-Date) (Get-Date).AddYears(1)).TotalMinutes
    )

    if ($GetParams.IsPresent) {
        Return $validParams | Sort-Object
    } else {
        $MinutesOk = $false
        $validParams | Foreach-Object {
            If ($Minutes -like $_) { $MinutesOk = $true }
        }

        Return $MinutesOk
    }
}
Function Get-SKHelperValidateCommandParam {
<#
.SYNOPSIS
    Helper to validate input in functions
#>
    PARAM (
        [Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )] $Command = "",
        [Parameter(Mandatory=$false)] [switch] $GetParams
    )
    $validParamsDevices = @(
        "TurnOn",
        "TurnOff",
        "Synchronize",
        "CancelSemiauto",
        "TurnOnFake",
        "TurnOfFfake",
        "Dim/0",
        "Dim/10",
        "Dim/20",
        "Dim/30",
        "Dim/40",
        "Dim/50",
        "Dim/60",
        "Dim/70",
        "Dim/80",
        "Dim/90",
        "Dim/100",
        "DimFake/0",
        "DimFake/10",
        "DimFake/20",
        "DimFake/30",
        "DimFake/40",
        "DimFake/50",
        "DimFake/60",
        "DimFake/70",
        "DimFake/80",
        "DimFake/90",
        "DimFake/100",
        "Synchronize"
    )
    $validParamsOther = @(
        "AddValue?Value=*",
        "SetValueRelative?Value=*&Action=*&MinValue=*&MaxValue=*",
        "AddFutureValue?Value=*&TimeStamp=*",
        "CommandQueue?Operation=*&Target=*",
        "Activate",
        "EntityLogentries?Maxcount=*&NewerThan=*",
        "EntityLogentries/Latest"
    )

    if ($GetParams.IsPresent) {
        Return $validParamsDevices | Sort-Object
    } else {
        $CommandOk = $false
        $validParamsDevices | Foreach-Object {
            If ($Command -like $_) { $CommandOk = $true }
        }

        $validParamsOther | Foreach-Object {
            If ($Command -like $_) { $CommandOk = $true }
        }

        Return $CommandOk
    }
}

Function Get-SKHelperValidateRelativeCommandParam {
<#
.SYNOPSIS
    Helper to validate input in functions
#>
    PARAM (
        [Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )] $Command = "",
        [Parameter(Mandatory=$false)] [switch] $GetParams
    )
    $validParams = @(
        "inc",
        "increase",
        "add",
        "plus",
        "dec",
        "decrease",
        "subtract",
        "minus"
    )
    if ($GetParams.IsPresent) {
        Return $validParams | Sort-Object
    } else {
        $CommandOk = $false
        $validParams | Foreach-Object {
            If ($Command -like $_) { $CommandOk = $true }
        }

        Return $CommandOk
    }
}


Function Get-SKHelperUrlEncodedString {
<#
.SYNOPSIS
    Helper to URL encode a string
#>
    PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )] [string] $inputString
    )

    [Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
    Return [web.httputility]::urlencode($inputString.Trim())
}

Function Get-SKHelperRest {
<#
.SYNOPSIS
    Helper to get SwitchKing info via REST
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$true)]  [string] $RestCall,
        [Parameter(Mandatory=$true)]  [string] $ArrayName,
        [Parameter(Mandatory=$false)] [string] $ArrayLoop = ""
	)

    $xml = Invoke-SKHelperRawRestCall -restCall $RestCall -SwitchKing $SwitchKing

    if ($xml -eq $false) {
        $info = $false
    } else {
        $info = @()

        if ($ArrayLoop -eq "" ) {
            $xml."$($ArrayName)" | ForEach-Object {
                $info += $_
            }
        } else {
            $xml."$($ArrayName)"."$($ArrayLoop)" | ForEach-Object {
                $info += $_
            }
        }
    }

    Return $info
}

Function Set-SKHelperRest {
<#
.SYNOPSIS
    Helper to set SwitchKing info via REST
#>
    PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [string] $restCall
    )

    $xml = Invoke-SKHelperRawRestCall -restCall "$($restCall)" -SwitchKing $SwitchKing

    If ( [string]$xml.string."#text" -eq "OK" ) {
        Return $true
    } else {
        Write-Verbose "Error calling SwitchKing: $($xml.string.'#text')"
        Return $false
    }
}

Function Add-SKHelperRest {
<#
.SYNOPSIS
    Helper to add data to SwitchKing via REST
#>
    PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [string] $restCall
    )

    $xml = Invoke-SKHelperRawRestCall -restCall "$($restCall)" -SwitchKing $SwitchKing

    Return $xml.RESTOperationResult
}

Function Invoke-SKHelperRawRestCall {
<#
    .SYNOPSIS
        Helper to talk to REST-API
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
		[string][alias("REST", "Call")] $restCall,
        [switch] $Binary
	)

    TRY {
        # Compose URL
        $url = "$($SwitchKing.URL)/$($restCall)"

        # Set credentials
        $skCredentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $SwitchKing.User, $SwitchKing.SecurePassword

        # Run the web request        
        $webRequest = Invoke-WebRequest -Uri $url -Credential $skCredentials -Method Get -ContentType "application/x-www-form-urlencoded"

        if ($Binary.IsPresent) {
            Return $webRequest
        } else {
            # Decode html in response
            [System.Reflection.Assembly]::LoadWithPartialName("System.web")
            $webRequest = [string] $webRequest
            $webRequest = [system.web.httputility]::htmldecode( $webRequest )
        
            # Return as XML
            Return  $webRequest
        }
    }
    CATCH {
        Write-Verbose "Error calling SwitchKing: $($error[0])"
        Return $false
    }

}

#endregion Helpers
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#region Generic

Function Get-SwitchKingInfo {
<#
    .SYNOPSIS
        Returns an object with all connection and server information needed for SwitchKing-CMD'lets
    .DESCRIPTION
        The function will test a connection to SwitchKing. If connection is OK it will return some information about the server and the credentials beeing used.
    .EXAMPLE
        $MySkConnection = Get-SwitchKingInfo -ServerName "server.home.local" -User "MyUser" -Password "SecretPassword"
        Using Plain Text password
    .EXAMPLE
        $MySkConnection = Get-SwitchKingInfo -ServerName "server.home.local" -User "MyUser" -SecurePassword (Read-Host -Prompt "Enter password" -AsSecureString)
        Gives the user a prompt to enter password
    .PARAMETER ServerName
        Name or IP to server
    .PARAMETER Port
        The port number to use
    .PARAMETER User
        Username defined in SwitchKing
    .PARAMETER Password
        Password defined in SwitchKing as Plain Text
    .PARAMETER SecurePassword
        Password defined in SwitchKing as Secure String
#>
  	PARAM (
		[Parameter(Mandatory=$false)][alias("Server", "IP")][ValidateNotNullOrEmpty()] [string] $ServerName = "localhost",
        [Parameter(Mandatory=$false)][ValidateRange(1,65535)] [int] $Port = 8800,
        [Parameter(Mandatory=$true) ][ValidateNotNullOrEmpty()] [string] $User,
        [Parameter(Mandatory=$false) ][alias("Pass", "PlainTextPassword")] [string] $Password = "",
        [Parameter(Mandatory=$false) ][alias("SecurePass")] [SecureString] $SecurePassword = $null
	)

    if ($Password -eq "" -and $SecurePassword -eq $null) {
        Throw "Missing Password or SecurePassword!"
    }

    # Plain text is used if SecurePassword isnt available
    if ($SecurePassword -eq $null) {
        $SecurePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
    }

    $SwitchKing = [PSCustomObject] @{
        Url = "http://$($serverName):$($port)"
        User = $User
        SecurePassword = $SecurePassword
    }

    Write-Verbose "Testing secure connection to $($SwitchKing.url)"
    $SystemSettings = Get-SwitchKingSystemSettings -SwitchKing $SwitchKing 

    If ($SystemSettings -eq $false) {
        $connectionOk = $false
        Return $false
    } else {
        $Location = ($SystemSettings | Where-Object { $_.Name -eq "CurrentLocationCoord" }).Value -split "&&"
        $LocationLat = $Location[0]
        $LocationLong = $Location[1]
    
        $SwitchKing = [PSCustomObject] @{
            Url = $SwitchKing.Url
            User = $SwitchKing.User
            SecurePassword = $SwitchKing.SecurePassword
            ServerVersion = ($SystemSettings | Where-Object { $_.Name -eq "ServerVersion" }).Value
            ServerIdentity = ($SystemSettings | Where-Object { $_.Name -eq "ServerIdentity" }).Value
            ServerDbVersion = ($SystemSettings | Where-Object { $_.Name -eq "CurDbVersion" }).Value
            ServerLocationLat = $LocationLat
            ServerLocationLong = $LocationLong
            ServerPlatform = ($SystemSettings | Where-Object { $_.Name -eq "Platform" }).Value
        }

        Return $SwitchKing

    }
}

#endregion Generic
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#region Get status

Function Get-SwitchKingSystemSettings {
<#
    .SYNOPSIS
        Returns System Settings
    .EXAMPLE
        Get-SwitchKingSystemSettings -SwitchKing ( Get-SwitchKingInfo -ServerName "server.home.local" -User "MyUser" -Password "SecretPassword" )
    .EXAMPLE
        $MySkConnection | Get-SwitchKingSystemSettings
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing
	)

    Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "SystemSettings" -ArrayName "ArrayOfRESTSystemSetting" -ArrayLoop "RESTSystemSetting"
}

Function Get-SwitchKingDevices {
<#
    .SYNOPSIS
        Returns device(s)
    .EXAMPLE
        $MySkConnection | Get-SwitchKingDevices
        Return all devices
    .EXAMPLE
        Get-SwitchKingDevices -SwitchKing $MySkConnection -DeviceID 12
        Return device with ID 12
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo¨s
    .PARAMETER DeviceID
        If you only want info from one device
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$false)] [alias("ID", "Device")] [int] $DeviceID = -1
	)

    if ($DeviceID -eq -1) {
        Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "Devices" -ArrayName "ArrayOfRESTDevice" -ArrayLoop "RESTDevice"
    } else {
        Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "Devices/$($DeviceId)" -ArrayName "RESTDevice"
    }
}

Function Get-SwitchKingDeviceGroups {
<#
    .SYNOPSIS
        Returns group(s)
    .EXAMPLE
        $MySkConnection | Get-SwitchKingDeviceGroups
        Return all groups
    .EXAMPLE
        Get-SwitchKingDeviceGroups -SwitchKing $MySkConnection -GroupID 12
        Return group with ID 12
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
    .PARAMETER GroupID
        If you only want info from one group
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$false)] [alias("ID", "Group")] [int] $GroupID = -1
	)

    if ($GroupID -eq -1) {
        Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "DeviceGroups" -ArrayName "ArrayOfRESTDeviceGroup" -ArrayLoop "RESTDeviceGroup"
    } else {
        Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "DeviceGroups/$($GroupID)" -ArrayName "RESTDeviceGroup"
    }
}

Function Get-SwitchKingDataSources {
<#
    .SYNOPSIS
        Returns datasource(s)
    .EXAMPLE
        $MySkConnection | Get-SwitchKingDataSources
        Return all datasources
    .EXAMPLE
        Get-SwitchKingDataSources -SwitchKing $MySkConnection -DataSourceID 12
        Return datasource with ID 12
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
    .PARAMETER DataSourceID
        If you only want info from one datasource
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$false)][alias("ID", "SourceID", "Source")] [int] $DataSourceID = -1
	)

    if ($DataSourceID -eq -1) {
        Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "DataSources" -ArrayName "ArrayOfRESTDataSource" -ArrayLoop "RESTDataSource"
    } else {
        Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "DataSources/$($DataSourceID)" -ArrayName "RESTDataSource"
    }
}

Function Get-SwitchKingDataSourceValues {
<#
    .SYNOPSIS
        Returns value(s) from a single DataSource
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$true)][alias("ID", "SourceID", "Source")] [int] $DataSourceID,
        [Parameter(Mandatory=$false)][alias("Count", "Max")] [int] $maxCount = 10,
        [Parameter(Mandatory=$false)][alias("Newer")] [DateTime][ValidateScript({$_ -le (Get-Date)})] $newerThan = (Get-Date).AddHours(-1)
	)

    Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "datasources/$($DataSourceID)/values?maxcount=$($maxCount)&newerthan=$(Get-SKHelperUrlEncodedString $newerThan)" -ArrayName "ArrayOfRESTDataSourceValue" -ArrayLoop "RESTDataSourceValue"
}

Function Get-SwitchKingDataSourceGraph {
<#
    .SYNOPSIS
        Create a PNG with a graph
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$true)][alias("ID", "SourceID", "Source")] [int] $DataSourceID,
        [Parameter(Mandatory=$false)][alias("X")] [int] $width = 800,
        [Parameter(Mandatory=$false)][alias("Y")] [int] $height = 600,
        [Parameter(Mandatory=$false)][alias("History", "Minutes")][ValidateScript({Get-SKHelperValidateMinutesParam -Minutes $_ })] [Int] $minutesOfHistory = 60,
        [Parameter(Mandatory=$true)][string] $filePath
	)

    $graph = Invoke-SKHelperRawRestCall -SwitchKing $SwitchKing -RestCall "datasources/$($DataSourceID)/graph.png?width=$($width)&height=$($height)&minutesofhistory=$($minutesOfHistory)" -Binary
    if ($graph -ne $false) {
        Return Set-Content -Path $filePath -Encoding Byte -Value $graph.content
    } else {
        Return $false
    }
}

Function Get-SwitchKingSenarios {
<#
    .SYNOPSIS
        Returns scenarios
    .EXAMPLE
        $MySkConnection | Get-SwitchKingSenarios
        Return all scenarios
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing
	)

    Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "Scenarios" -ArrayName "ArrayOfRESTScenario" -ArrayLoop "RESTScenario"
}

Function Get-SwitchKingSystemModes {
<#
    .SYNOPSIS
        Returns System Modes
    .EXAMPLE
        Get-SwitchKingSystemModes -SwitchKing $MySkConnection
        Return all System Modes
    .EXAMPLE
        $MySkConnection | Get-SwitchKingSystemModes -Active
        Return active System Mode
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
    .PARAMETER Active
        Only return Active System Mode
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$false)] [switch] $Active
	)

    #Todo: If (Test-OkVersion -MinRequired "3.0.0" -CurrentVersion $SwitchKing.ServerVersion -eq $false) { Throw "Need at least 3.0.0" }

    If ( $Active.IsPresent ) {
        Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "systemmodes/active" -ArrayName "RESTSystemMode"
    } else {
        Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "systemmodes" -ArrayName "ArrayOfRESTSystemMode" -ArrayLoop "RESTSystemMode"
    }
}

Function Get-SwitchKingEvents {
<#
    .SYNOPSIS
        Get upcoming event(s) from SwitchKing
    .EXAMPLE
        Get-SwitchKingEvents -SwitchKing $MySkConnection
        Return all upcoming events
    .EXAMPLE
        $MySkConnection | Get-SwitchKingSystemModes -maxCount 5
        Get 5 upcoming events
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
    .PARAMETER maxCount
        Limit number of events
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [alias("Max", "Count")] [int] $maxCount = 10
	)

    Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "Events/future?maxCount=$($maxCount)" -ArrayName "ArrayOfRESTEvent" -ArrayLoop "RESTEvent"
}

Function Get-SwitchKingInterceptedEvents {
<#
    .SYNOPSIS
        Get intercepted events from SwitchKing (From Duo / Telldus Center)
    .EXAMPLE
        Get-SwitchKingInterceptedEvents -SwitchKing $MySkConnection
        Get intercepted events
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
#>
	PARAM (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("SK")] [PSCustomObject] $SwitchKing
	)

    Return Get-SKHelperRest -SwitchKing $SwitchKing -RestCall "Events/intercepted" -ArrayName "ArrayOfRESTMonitoredEntityEvent" -ArrayLoop "RESTMonitoredEntityEvent"
}

#endregion Get status
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#region Set status

Function Get-SwitchKingCommands {
<#
    .SYNOPSIS
        Get valid SwitchKing Commands
    .EXAMPLE
        Get-SwitchKingCommands
        Return an array with commands
#>
    Return Get-SKHelperValidateCommandParam -GetParams
}

Function Get-SwitchKingRelativeCommands {
<#
    .SYNOPSIS
        Get valid SwitchKing Relative Commands
    .EXAMPLE
        Get-SwitchKingRelativeCommands
        Return an array with commands
#>
    Return Get-SKHelperValidateRelativeCommandParam -GetParams
}

Function Get-SwitchKingMinutes {
<#
    .SYNOPSIS
        Get valid SwitchKing minutes
    .EXAMPLE
        Get-SwitchKingMinutes
        Return an array with minutes
#>
    Return Get-SKHelperValidateMinutesParam -GetParams
}

Function Set-SwitchKingDevice {
<#
    .SYNOPSIS
        Set status on a device
    .EXAMPLE
        Get-SwitchKingDevices -SwitchKing $MySkConnection | Where-Object { $_.GroupName -eq "Outside Lights" } | Set-SwitchKingDevice -Command "turnoff" -SwitchKing $MySkConnection
        Send the command TurnOff to a devices in a group named 'Outside Lights'
    .EXAMPLE
        Get-SwitchKingDevices -SwitchKing $MySkConnection | Where-Object { $_.Name -eq "Kitchen Light" } | Set-SwitchKingDevice -Command "dim/40" -SwitchKing $MySkConnection
        Send the command Dim/40 to the device named 'Kitchen Light'
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
    .PARAMETER Device
        Device(s) returned from Get-SwitchKingDevice
    .PARAMETER Command
        Command to send, user Get-SwitchKingCommands to list valid commands
#>
	PARAM (
        [Parameter(Mandatory=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [PSCustomObject] $Device,
        [Parameter(Mandatory=$true)] [ValidateScript({Get-SKHelperValidateCommandParam -Command $_ })] [string] $Command
	)

	BEGIN {}
	PROCESS {
		Foreach ($SkDevice in $Device) {
            If ($Command -like "*dim*/*" -and $SkDevice.SupportsAbsoluteDimLvl -eq $false) {
                Write-Verbose "Device ID $($SkDevice.ID) ($($SkDevice.Name)) doesn't support dim-commands"
                Return $false
            } else {
                Write-Verbose "Send $($Command) to device $($SkDevice.ID) ($($SkDevice.Name))"
                Return Set-SKHelperRest -SwitchKing $SwitchKing -RestCall "devices/$($SkDevice.ID)/$($Command)"
            }
		}
    }
    END {}
}

Function Set-SwitchKingDeviceGroup {
<#
    .SYNOPSIS
        Set status on a group
    .EXAMPLE
        Get-SwitchKingDeviceGroup -SwitchKing $MySkConnection | Where-Object { $_.Name -like "Inside*" } | Set-SwitchKingDeviceGroup -Command "turnoff" -SwitchKing $MySkConnection
        Send the command TurnOff to a group starting with 'Inside' in the name
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
    .PARAMETER DeviceGroup
        Device(s) returned from Get-SwitchKingDeviceGroups
    .PARAMETER Command
        Command to send, user Get-SwitchKingCommands to list valid commands
#>
	PARAM (
        [Parameter(Mandatory=$true)][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("Group")] [PSCustomObject] $DeviceGroup,
        [Parameter(Mandatory=$true)] [ValidateScript({Get-SKHelperValidateCommandParam -Command $_ })] [string] $Command
	)

	BEGIN {}
	PROCESS {
		Foreach ($SkDeviceGroup in $DeviceGroup) {
            Write-Verbose "Send $($Command) to device $($SkDeviceGroup.ID) ($($SkDeviceGroup.Name))"
            Return Set-SKHelperRest -SwitchKing $SwitchKing -RestCall "devicegroups/$($SkDeviceGroup.ID)/$($Command)"
		}
    }
    END {}
}

Function Set-SwitchKingScenario {
<#
    .SYNOPSIS
        Activate a Scenario
    .EXAMPLE
        Get-Get-SwitchKingSenarios -SwitchKing $MySkConnection | Where-Object { $_.Name -eq "Movie Time" } | Set-Get-SwitchKingSenario -SwitchKing $MySkConnection
        Activate the scenario named 'Movie Time'
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
    .PARAMETER Scenario
        Scenario returned from Get-SwitchKingSenarios
#>
	PARAM (
        [Parameter(Mandatory=$true)][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )] [PSCustomObject] $Scenario
	)

    Write-Verbose "Set scenario $($Scenario.ID) ($($Scenario.Name)) as active"
    Return Set-SKHelperRest -SwitchKing $SwitchKing -RestCall "commandqueue?operation=changescenario&target=$($Scenario.ID)"
}

Function Set-SwitchKingSystemMode {
<#
    .SYNOPSIS
        Activate a System Mode
    .EXAMPLE
        Get-Get-SwitchKingSystemModes -SwitchKing $MySkConnection | Where-Object { $_.Name -eq "Home" } | Set-Get-SwitchKingSenario -SwitchKing $MySkConnection
        Activate the System Mode 'Home'
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
    .PARAMETER SystemMode
        Scenario returned from Get-SwitchKingSystemMode
#>
	PARAM (
        [Parameter(Mandatory=$true)][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][alias("Mode")] [PSCustomObject] $SystemMode
	)

    Write-Verbose "Set SystemMode $($SystemMode.ID) ($($SystemMode.Name)) as active"
    Return Set-SKHelperRest -SwitchKing $SwitchKing -RestCall "commandqueue?operation=changesystemmode&target=$($SystemMode.ID)"
}

#endregion Set status
# ------------------------------------------------------------------------------------------------------------
#region Add Values
Function Add-SwitchKingDataSourceValue {
<#
    .SYNOPSIS
        Add A value to a Data Source
    .EXAMPLE
        Get-SwitchKingDataSources -SwitchKing $MySkConnection | Where-Object { $_.Name -eq "Powershell Test" } | Add-SwitchKingDataSourceValue -Value 4 -SwitchKing $MySkConnection
        Add value 4 to a datasource named 'Powershell Test'
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
    .PARAMETER DataSource
        Result from Get-SwitchKingDataSources
    .PARAMETER Value
        Value to add
#>
	PARAM (
        [Parameter(Mandatory=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [alias("ID", "Source")] [PSCustomObject] $DataSource,
        [Parameter(Mandatory=$true)] [string] $Value
	)

	BEGIN {}
	PROCESS {
		Foreach ($SkDataSource in $DataSource) {
            Write-Verbose "Adding value $($Value) to DataSource ID $($SkDataSource.ID) ($($SkDataSource.Name))"
            Return Add-SKHelperRest -SwitchKing $SwitchKing -RestCall "datasources/$($SkDataSource.ID)/addvalue?value=$(Get-SKHelperUrlEncodedString $Value)"
		}
    }
    END {}
}

Function Add-SwitchKingDataSourceRelativeValue {
<#
    .SYNOPSIS
        Add a relative value to a Data Source
    .EXAMPLE
        Get-SwitchKingDataSources -SwitchKing $MySkConnection | Where-Object { $_.Name -eq "Powershell Test" } | Add-SwitchKingDataSourceRelativeValue -Value 3 -SwitchKing $MySkConnection -Action '?' -minValue 2 -maxValue 5
        Add a relative value to a datasource named 'Powershell Test'
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
    .PARAMETER DataSource
        Result from Get-SwitchKingDataSources
    .PARAMETER Value
        Relative value to add
    .PARAMETER Action
        Command to send, user Get-SwitchKingRelativeCommands to list valid commands
    .PARAMETER minValue
        Min limit to add
    .PARAMETER maxValue
        Min limit to add
#>

	PARAM (
        [Parameter(Mandatory=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [alias("ID", "Source")] [PSCustomObject] $DataSource,
        [Parameter(Mandatory=$true)] [int] $Value,
        [Parameter(Mandatory=$true)] [string] $Action,
        [Parameter(Mandatory=$true)][alias("Min")] [int] $minValue,
        [Parameter(Mandatory=$true)][alias("Max")] [int] $maxValue
	)


	BEGIN {}
	PROCESS {
		Foreach ($SkDataSource in $DataSource) {
            Write-Verbose "Adding Relative Value $($Value) to DataSource ID $($SkDataSource.ID) ($($SkDataSource.Name))"

            Return Add-SKHelperRest -SwitchKing $SwitchKing -RestCall "datasources/$($SkDataSource.ID)/setvaluerelative?value=$($Value)&action=$($Action)&minValue=$($minValue)&maxValue=$($maxValue)"
		}
    }
    END {}
}

Function Add-SwitchKingDataSourceFutureValue {
<#
    .SYNOPSIS
        Add A value to a Data Source
    .EXAMPLE
        Get-SwitchKingDataSources -SwitchKing $MySkConnection | Where-Object { $_.Name -eq "Powershell Test" } | Add-SwitchKingDataSourceFutureValue -Value (Get-Random -Minimum 1 -Maximum 20) -SwitchKing $MySkConnection -timeStamp (Get-Date).AddMinutes(2)
        Add a random value between 1 and 20 to a datasource named 'Powershell Test'
    .PARAMETER SwitchKing
        Result from Get-SwitchKingInfo
    .PARAMETER DataSource
        Result from Get-SwitchKingDataSources
    .PARAMETER Value
        Value to add
    .PARAMETER timeStamp
        Future time to add the value
#>
	PARAM (
        [Parameter(Mandatory=$true )][alias("SK")] [PSCustomObject] $SwitchKing,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [alias("ID", "Source")] [PSCustomObject] $DataSource,
        [Parameter(Mandatory=$true)] [string] $Value,
        [Parameter(Mandatory=$true)] [DateTime][ValidateScript({$_ -gt (Get-Date)})] $timeStamp

	)

	BEGIN {}
	PROCESS {
		Foreach ($SkDataSource in $DataSource) {
            Write-Verbose "Adding Future Value $($Value) to DataSource ID $($SkDataSource.ID) ($($SkDataSource.Name)) at $($timeStamp)"

            Return Add-SKHelperRest -SwitchKing $SwitchKing -RestCall "datasources/$($SkDataSource.ID)/addfuturevalue?value=$(Get-SKHelperUrlEncodedString $Value)&timestamp=$(Get-SKHelperUrlEncodedString $timeStamp)"
		}
    }
    END {}
}

#endregion Add Values
# ------------------------------------------------------------------------------------------------------------
# Export all functions except Helpers
Get-ChildItem Function: | Where-Object { $_.ModuleName -eq "SwitchKing" -and $_.Name -notlike "*-SKHelper*" } | ForEach-Object {
    Export-ModuleMember -Function $_.Name -Alias $_.Name.Replace("SwitchKing", "SK")
}
# ------------------------------------------------------------------------------------------------------------
#>

Get-ADSites and create Site Based Collections

Missing an easy way to get AD-Sites from Powershell?

They are listed under Sistes/Configuration with the objectClass = Site. So a simple LDAP-query does the trick.

Get-ADSites {
	Return Get-AdObject -LdapFilter "(ObjectClass=site)" -SearchBase "CN=Sites,CN=Configuration,$((Get-AdDomain).DistinguishedName)"
}

Then, with these sites it’s easy to create Device Collections in ConfigMgr based on what AD-Site the client reports.

Get-ADSites | ForEach-Object {
	$newCollection = New-CMDeviceCollection -Name "AD-Site $($_.Name)" -LimitingCollectionID "SMS00001" -RefreshType Periodic -RefreshSchedule (New-CMSchedule -RecurInterval Days -RecurCount 1 -Start (Get-Date))
	$query = "SELECT * FROM SMS_R_System WHERE SMS_R_System.ADSiteName = '$($_.Name)'"
	Add-CMDeviceCollectionQueryMembershipRule -CollectionId $newCollection.CollectionID -QueryExpression $query -RuleName "ADSite-$($_.Name)"
}

Reset local admin password

You probably want to have random passwords on all your local admin accounts… Wrote a function that generates a complex (and readable) password and another function that sets the local admin password.

Function Get-RandomPassword {
	PARAM (
		$pwdMask = "####-####-####-####-####",
		$pwdCharacters = "abcdefghjkmnopqrstuvwxy23456789ABCDEFGHJKLMNPQRTUVWXYZ"
	)

	$newPassword = ""
	(0 .. (($pwdMask.Length)-1) ) | ForEach-Object  {
		If ( $pwdMask.Chars($_) -eq "#" ) {
			$rndChar = Get-Random -Minimum 0 -Maximum $pwdCharacters.Length
	 		$newPassword += $pwdCharacters.Chars($rndChar)
		} else {
			$newPassword += $pwdMask.Chars($_)
		}
	}

	Return $newPassword
}


Function Set-LocalAdminPassword {
	PARAM (
		[string] $computerName,
		[string] $newPassword
	)

	$adminAccountName = (Get-WmiObject Win32_UserAccount -Filter "LocalAccount = True AND SID LIKE 'S-1-5-21-%-500'" -ComputerName $computerName | Select-Object -First 1 ).Name
	TRY {
		Write-Verbose "Reset password for $($computerName)\$($adminAccountName) to $($newPassword)"
		$adminAccount = [adsi]"WinNT://$($computerName)/$($adminAccountName),user"
		$adminAccount.setPassword($newPassword)
		Return $true
	}
	CATCH {
		Return $false
	}
}

This is how to reset password on a single computer

Set-LocalAdminPassword -computerName "SOMEPC" -newPassword (Get-RandomPassword)

And if you want to process a list of computers (that are online) from the AD

Import-Module ActiveDirectory
$newPwds = @{}
Get-ADComputer -LDAPFilter "(name=PC00*)" | ForEach-Object {
	$computerName = $_.Name
	If (Test-Connection -ComputerName $computerName -Count 1 -ErrorAction SilentlyContinue) {
		$randomPwd = Get-RandomPassword
		If (Set-LocalAdminPassword -computerName $computerName -newPassword $randomPwd ) {
			$newPwds.Add($computerName, $randomPwd)
		}
	}
}

$newPwds | Format-Table -AutoSize

Remove all direct memberships from collections

If you are using direct memberships to speed up ConfigMgr, you probably want to clean it up when your collection query is good to go…

The problem is to find all direct memberships since Get-CMUserCollectionDirectMembershipRule doesn’t allow wildcards. But there is a WMI-class with all members…

Function Remove-AllDirectMemberships {
	PARAM (
		[string] $sccmSite = "ABC",
		[string] $collectionNameFilter = "SomePrefix*"
	)

	Get-CMUserCollection -Name $collectionNameFilter | ForEach-Object {
		$Collection = $_
		Write-host "$($Collection.Name)"
		Get-WmiObject -Class $_.MemberClassName -Namespace "ROOT\SMS\Site_$($sccmSite)" | Foreach-Object {
			If ($_.IsDirect -eq $true ) {
				Write-host "- Removing $($_.ResourceID)"
				Remove-CMUserCollectionDirectMembershipRule -CollectionId $Collection.CollectionID -ResourceId $_.ResourceID -Force
			}
		}
	}
}

Default Powershell Execution Policy

You can use a GPO to set the ExecutionPolicy to a static value on all machines.

But what if you want to default it to something and then let the users have the ability to change it?

Group Policy Preferences is the easy answer.

Create a GPO targeting your machines and then create a entry under Computer Configuration -> Preferences -> Windows Settings -> Registry that looks like this:
130917-execpolicy-1

130917-execpolicy-2

The paths are:
Key: SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell
Value Name: ExecutionPolicy
ValueData: RemoteSigned

With this setup a machine that doesn’t have any specific exec policy (then the reg key doesn’t exist) setup will get RemoteSigned.
(Another way is to do this with a startup script for the computer)