Tag Archives: Custom Functions

Find where a certificate is installed

Some organizations use wildcard certificates. The problem with these certificates is that they start to lead a life on it’s own. So when the time comes that such a certificate is about to expire one can wonder where is this certificate installed? Luckily we have Powershell to save us from having to open all certificate stores on all servers in the domain. At first I was  thinking maybe the easiest way could be to have a Powershell remoting session. But that would require all servers to have Powershell remoting enabled. Unfortunately not all servers have it enabled or have a reasonable Powershell version to start with. So just for documenting purpose I’ll write down the command to find a certificate.

A certificate has a thumbprint. This thumbprint is equal on all servers as it part of the certificate, much like the serial number.  So you can easily find the thumbprint where your looking for running the command on a server where the certificate is present.

Just copy the thumbprint and enter it into the command to find the certificate like the on below

Invoke-Command {gci cert:\Localmachine\My | ? {$_.Thumbprint -eq "FF9C130AE6C1D18FAC43AF0416E44B7011307545"}} -ComputerName #COMPUTERNAME#

Replace #COMPUTERNAME#  for the computername you would want to question. Now this is all good but like I mentioned before I would want to find certificate regardless of Operation system contstrains or whether Powershell Remoting is enabled or not. So this script would process that accordingly. Change the thumbprint to the thumbprint of your certificate. The script export all servers where the certificate with the given thumbprint is present. Please feel free to modify this to your needs.

Function Get-Cert( $computer=$env:computername ){
    #Sets ReadOnly flag upon external Certificate store.
	$ro=[System.Security.Cryptography.X509Certificates.OpenFlags]"ReadOnly"
	#Opens LocalMachine store. You could potentially enter a different Cert store. 
	$lm=[System.Security.Cryptography.X509Certificates.StoreLocation]"LocalMachine"
	#Opens personal certificate store from local machine. 
	$store=new-object System.Security.Cryptography.X509Certificates.X509Store("\\$computer\My",$lm)
	#Establish connection with remote PC
    $store.Open($ro)

	#Display certificates in console or variable
	$store.Certificates

}

#Opens list
$Thumbprint = "FF9C130AE6C1D18FAC43AF0416E44B7011307545" 
$List = @()

#Servers reset their machine password within a specific timeframe
$Servers = Get-ADComputer -Filter * -Properties OperatingSystem,PasswordLastSet | ?{$_.OperatingSystem -match "Server" -and $_.PasswordLastSet -gt (Get-Date).AddDays(-60)}

Foreach ($Server in $Servers)
	{
	$Server.DNSHostName 
	If ((Get-Cert $Server.DNSHostname | ? {$_.Thumbprint -eq $Thumbprint}).Count -ne 0)
		{
		$List += $Server.DNSHostName
		}
	}

$List | % {AC -Value $_ -Path ".\ComputersWithCertificate.txt"}

Powershell ShareFile

When a software developer introduces a Powershell module or snapin I’m always excited about it. So Citrix released a Powershell snapin for ShareFile. There are some good thing about it, some bad things and one ugly thing.

The bad

So Citrix says that the ShareFile module is without the guarantee that it’ll actually work. See it like a beta product. This directly indicates that you’ll find very few documentation about. But even more worrying is that you’re on your own because it is unsupported. Citrix released a Powershell version but at the same time there’s a C# and a Java module. They’ve build this on a REST API. This is beautiful because in a way you’re not really bound to the ShareFile modules. As long as you can talk to the REST API. The API is on version 3 so I think it’s pretty stable and should lose it “in development” state.  But the lack of good documentation and

The Ugly

Well first and foremost, an administrator can create a SFPS file. This file can be used to authenticate to the ShareFile REST API. So you can create a carefree file that could be used by a first in line administrator or a script. And so you don’t have to share the credentials.  By default there’s no expiration date. Meaning that if this file falls in the wrong hands, one can truly demolish your entire environment. A lazy administrator could potentially compromise his entire environment. Via Powershell you can actually get the files in the repository of a ShareFile User. This is a extreme risk of which many administrators are unaware.

The Good

Well there’s a lot good about this. In most cases if there’s a REST API, the GUI is build upon that. So anything you can do in the GUI, can be done using the API. This is an incredible feature set. You can completely manage your environment using the API. I created some scripts to manage the ShareFile API. Right now the community is rather small, but as soon as this grows I’m sure there’s going to be a large knowledge base where you can find help.

If you get the authentication right and the scripts ready I’m sure that this will ease the pressure of the back of the administrators. I’m quite positive anyway.

vCenter VM reboot script

Once in a while I run into problems that are really fun resolving. A colleague  of mine said that he was early today to fix the XenApp servers that did not reboot successfully. He said that since the storage of the Citrix Provisioning server was moved from flash to SAS the XenApp servers sometimes cannot boot. A shortage in IOPS is supposed to be the cause of this trouble. However all Provisioning servers combined consume over a TeraByte of storage. During the day this fast storage does not is nearly idle. Only during boot of the XenApp machines it is used in an appropriate manner. So that is quite costly.

VMWare created CMDlet’s the right way. You can basically do anything you can in GUI via Powershell using the PowerCLI.  The PowerCLI is an incredible tool in automatising stuff. We have RES Automation Manager. So I put the bits together and created A Powershell script in RES Automation Manager that runs every night. This script uses a few parameters which you can see in the code in the top section of the script. The script heavily relies on PowerCLI 6.0 This version works for any vCenter server.

The script connects to a VIserver (in this case the vCenter server). Next it gathers a list of all servers. Then it filters server based on the condition if it is PoweredOn and it matches on Name. Next it gets the VMware Tools status. If the status is NotRunning it will be applicable for a restart. The servers will be rebooted in a staggered way. So every two minutes a server will reboot. There’ll be a second attempt if the first attempt fails. At last it sends an e-mail with all server that fit the condition the first time, the second time and server that did not rebooted well.

I hope you can use it.

# Adds the base cmdlets
Add-PSSnapin VMware.VimAutomation.Core
$Recipients = $[Recipients]
$VIServer = "$[VIServer]"
$NotCondition = "$[NotCondition]"
$Condition = "$[Condition]"
$Username = [Environment]::UserName
$Hostname = $env:COMPUTERNAME
$Mailserver = "$[MailServer]"
$Level = "0"
Connect-VIServer $VIServer

#List bouwen van VM's die voldoen aan de voorwaarden
$List =@(Get-VM | ? {$_.Name -match $Condition -and $_.PowerState -match "PoweredOn" -and $_.Name -notmatch $NotCondition} | Get-VMGuest | ? {$_.State -match "NotRunning"})

#Check of de PowerCLi wel geinstalleerd is
If (((Get-PSSnapin).name -match "VMware.VimAutomation") -eq $Null){$Body = "De computer $Hostname heeft geen PowerCLI geinstalleerd staan. Installeer PowerCLI en probeer het opnieuw "; $Level = "1"}

#Check of er een verbinding is met de VI server
If ($global:DefaultVIServer -eq $null -and $Level -ne "1"){$Body = "Kon geen verbinding krijgen met de VCenter Server: $VIServer. Verander de waarde in het script als de servernaam veranderd is. Controleer of de user $UserName rechten heeft op de VCenter server. Controleer eventueel of de PowerCLI tools op de server wel compatible zijn met de Vcenter Server versie." ; $Level = "2"}

#Geen servers gevonden
If ($list.count -eq "0" -and $Level -eq "0"){$Body = "Er zijn geen server(s) gevonden welke voldoen aan de voorwaarden."}

#Actie bij wel servers gevonden
If ($list.count -gt "0" -and $Level -eq "0"){
$Body = "Er waren "+ $list.count +" server(s) die voldeden aan de voorwaarden. Het gaat om de server(s):`n`n"
$Body = "Er worden "+ $list.count +"servers herstart. De servers zijn:`n`n"
Foreach ($item in $list){$Body = $Body + $Item.vm + "`r"}

#Herstarten van servers
Foreach ($item in $list){Restart-VM -VM $Item.VM -Confirm:$False ; Start-Sleep -s 120}

#Check of er nu geen servers meer zijn die voldoen aan de voorwaarden
$List2 =@(Get-VM | ? {$_.Name -match $Condition -and $_.PowerState -match "PoweredOn" -and $_.Name -notmatch $NotCondition} | Get-VMGuest | ? {$_.State -match "NotRunning"})

#Actie bij wel servers gevonden die nog niet goed herstart zijn
If ($List2.Count -ne "0"){
$Body = $Body + "`n`n Er waren server die nog steeds niet goed herstart waren. De naam van deze server(s) waren:`n`n"
Foreach ($item2 in $list2){$Body = $Body + $Item2.vm + "`r"}
$Body = $Body + "`n`n Er worden geprobeerd de server opnieuw te herstarten."
Foreach ($item2 in $list2){Restart-VM -VM $Item2.VM -Confirm:$False ; Start-Sleep -s 120}
#Laatste check
$List3 =@(Get-VM | ? {$_.Name -match $Condition -and $_.PowerState -match "PoweredOn" -and $_.Name -notmatch $NotCondition} | Get-VMGuest | ? {$_.State -match "NotRunning"})
If ($List3.Count -ne "0"){
$Body = "Er zijn fouten gevonden bij het herstarten van servers. Zie onderaan de e-mail!!" + $Body
$Body = $Body + "`n`n De server die niet herstart zijn na twee pogingen zijn:`n`n"
Foreach ($item3 in $list3){$Body = $Body + $Item3.vm + "`r"}
}}}

#Mail versturen
Send-MailMessage -From "Rebootscript@domain.tld" -Subject "VMs rebooten" -To $Recipients -Body $Body -SmtpServer $MailServer

#Output naar Console in het geval dat er geen mail verstuurd is.
Write-host `n`n Mail Body`n
$Body

Resolving script for autocomplete

A servicedesk employee came to me with a problem with the autocomplete functionality in Outlook 2010. So I looked around for a bit to find some code. I came across a script that does the job. I added functionality so that it would work for the servicedesk. For the script to work, the user will need to have enough permissions to add permissions to any mailbox. The script is semi-automatic. So user interaction is needed.

Outlook 2003 used to have NK2 files which are corrupt from time to time. Then Microsoft replaced that functionality into a .dat with exact the same issues. Clearly in Outlook 2010 this problem still exists. The key difference is that Outlook 2010 and Outlook 2013 in combination with Exchange 2010 or later has a functionality called suggested contacts. This works similar to the autocomplete function only better. Contacts added to the suggested contacts will be in the autocomplete list. Imagine that by generating a list of all unique recipients in the sent item you’ll have a list that is satisfactory for the user.

Furthermore this functionality works regardless of the user profile. So in our case if a user uses Outlook Web App that user will also have that same functionality. There is a catch. This does not work as intended in combination with Res Workspace Manager and Zero-profiling. To get this right you can use another script.

This script gives write the LegacyDN as output on screen. This is needed for situaties where you have GAL segregation. Then it opens the control panel for e-mail on that server. Of course Outlook needs to be installed on the system. That you need to permit the script to use Outlook. You’ll get the warning as seen below:

Screen Shot 2015-04-16 at 13.55.28

Again if you use RES Workspace Manager with Zero-Profiling use another script.

#Import Modules,Snapins and functions
ipmo ActiveDirectory
Add-PSSnapin Microsoft.Exchange.Management.Powershell.E2010
Function AddTo-SuggestedContactsIfNotAlreadyAdded ($name, $email)
{

    if(($name -eq "") -or ($email -eq "") -or ($name -eq $null) -or ($email -eq $null)){
        return;
    }

    $name = $name.Replace("'", "").Replace("""", "")


    $contactAlreadyAdded = $false

    foreach ($elem in $global:alreadyAddedEmails) {
        if(($elem.ToLower() -match $email.ToLower())){
            $contactAlreadyAdded = $true
            Write-Host  ($global:counter)"/"($totalItems)  "SKIPPED " $name.PadRight(25," ") "-" $email
            return;
        }
    }

    if(!$contactAlreadyAdded )    {
        $newcontact = $contacts.Items.Add()
        $newcontact.FullName = $name
        $newcontact.Email1Address = $email
        $newcontact.Save()
        $global:alreadyAddedEmails += $email
        Write-Host ($global:counter)"/"($totalItems)  "ADDED   " $name.PadRight(25," ") "-" $email
    }
}

#Set Variables
$Admin = [Environment]::UserName
$choice2 = "y"
$Admindomain = $env:userdomain

#Set loop function
while ($choice2 -match  "y"){

#Request Username from interactive User
Write-Host "Voer de gebruikersnaam van de gebruiker in: " -NoNewline -F Cyan 
$Username = read-host 

#Test if user exist
$Testresult = $Null
$Testresult = Get-ADUser $Username -Properties *
While ($Testresult -eq $Null){
write-host "De opgegeven Username bestaat niet of is foutief ingevoerd. Toets de username in in het format zoals bijvoorbeeld OTHSBE02" -b Black -f Red
$Username = Read-Host "Voer de gebruikersnaam van de gebruiker in:" -NoNewline -F Cyan 
$Testresult = Get-ADUser $Username -Properties *}

Get-ADUser -Identity $Testresult.samaccountname -Properties legacyExchangeDN  | Select legacyExchangeDN

#Addmailbox permission to the admin user with retry
Add-MailboxPermission -Identity $Testresult.samaccountname -AccessRights "FullAccess" -User ("$AdminDomain"+"\"+"$Admin")
$Testresult2 = $null
$Testresult2 = Get-MailboxPermission -Identity $Testresult.samaccountname | ? {$_.User -match ("$AdminDomain"+"\\"+"$Admin")}
While ($Testresult2 -eq $Null){
write-host "De rechten op de mailbox zijn niet goed gezet. Het wordt nogmaals geprobeerd" -b Black -f Red
Add-MailboxPermission -Identity $Testresult.samaccountname -AccessRights "FullAccess" -User ("$AdminDomain"+"\"+"$Admin")
$Testresult2 = Get-MailboxPermission -Identity $Testresult.samaccountname | ? {$_.User -match ("$AdminDomain"+"\\"+"$Admin")}}


#Opens Mail control panel
C:\Windows\SysWOW64\control.exe mlcfg32.cpl

#Confirm that the admin have changed to Exchange profile accordingly.
$choice = $Null
 while ($choice -notmatch "[y|n]"){
     $choice = read-host "Heb je het Outlook profiel aangepast? (Y/N)"
      }


$outlook = new-object -com outlook.application
$olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
$namespace = $outlook.GetNameSpace("MAPI")
$sentItems = $namespace.getDefaultFolder($olFolders::olFolderSentMail)
$alreadyAddedEmails = @() #Empty Array
$counter = 0;
$totalItems = $sentItems.items.count;

Write-Host "Scanning through" $totalItems "emails in SentItems"

$contacts = $outlook.Session.GetDefaultFolder($olFolders::olFolderSuggestedContacts)



# Loop through all emails in SentItems
$sentItems.Items | % {

    #Loop through each recipient
    $_.Recipients | %{
        AddTo-SuggestedContactsIfNotAlreadyAdded $_.Name  $_.Address
    }
    $global:counter = $global:counter + 1
}

$outlook.Quit()

#Addmailbox permission to the admin user with retry
Remove-MailboxPermission -Identity $Testresult.samaccountname -AccessRights "FullAccess" -User ("$AdminDomain"+"\"+"$Admin")
$Testresult2 = Get-MailboxPermission -Identity $Testresult.samaccountname | ? {$_.User -match ("$AdminDomain"+"\\"+"$Admin")}
While ($Testresult2 -ne $Null){
write-host "De rechten op de mailbox zijn niet goed gezet. Het wordt nogmaals geprobeerd" -b Black -f Red
Remove-MailboxPermission -Identity $Testresult.samaccountname -AccessRights "FullAccess" -User ("$AdminDomain"+"\"+"$Admin")
$Testresult2 = Get-MailboxPermission -Identity $Testresult.samaccountname | ? {$_.User -match ("$AdminDomain"+"\\"+"$Admin")}}


#Present user with the choice to do another user.
$choice2 = read-host "Wilt u dit voor nog een user uitvoeren? (Y/N)"
}

 

Test whether PS Remoting is enabled

PSRemoting is a powerful way to manage remote computers. I often use this. However on some server PSRemoting is not enabled. A custom function can help you. When you import this in a Powershell session you can manage the server that don’t have PSRemoting enabled. I assume that you only have Windows Server 2008 and above servers in you domain.

First import the module or copy-paste it in you shell.

function Test-PSRemoting 
{ 
    param( 
        [Parameter(Mandatory = $true)] 
        $RemoteComputer 
    ) 
    
    try 
    {
		#Returns value 1 in a scriptblock on the remote computer
        $errorActionPreference = "Stop" 
        $result = Invoke-Command -ComputerName $RemoteComputer {1} 
    } 
    catch 
    { 
        Write-Verbose $_ 
        return $false 
    } 
     
    if($result -ne "1") 
    { 
        Write-Verbose "The remote computer: $RemoteComputer did not return the expected result. Please make sure you have enough privileges to connect to the computer." -f Red
        return $false 
    } 
    
    $true    
}

TestPSRemoting

Then to use it you can use the following code:

Import-Module ActiveDirectory
Import-Module ".\TestPSRemoting.psm1" -Force
$Servers = Get-ADComputer -Filter * -Properties OperatingSystem | Where {$_.OperatingSystem -match "Server"}
Write-host "There are " $Servers.count " servers found"
$DomainUser = ($env:userdomain+"\"+$env:username)
$secureString = Read-Host -AsSecureString "Enter your password"


Foreach ($Server in $Servers){
$Result = $null
$ServerName = $Server.Name
$Result = Test-PSRemoting $Server
If ($Result -eq $False){
Write-Host "PSRemoting will be enabled on server: $Server"
$Date = (Get-Date).AddMinutes(30)
schtasks /create /S $ServerName /RU $DomainUser /RP $secureString /SC Once /SD $date.ToString('dd"/"MM"/"yyy') /ST $date.ToString('HH":"mm') /TN "Enable PSRemoting" /TR "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe Enable-PSRemoting -Force" /F
}}

You need to be an Administrator of course or at least have enough permission to enable psremoting. If PSremoting is disabled an task will be scheduled to enable it within 30 minutes. You can enable PSRemoting remotely using the script. If you want to enable PSremoting on all workstations you’ll need to change the definition to something else. For example use a part of the DestinguishedName if an OU is called Workstations for example.

Good luck!

App-v Staging script

We are having performance issues with some virtualising some packages in App-v 5. There’s a package that needs it’s entire registry before it performs. When a user runs the starts the packages 3 to 4 times it starts to perform better. We have seen that the registry increased by 200 MB of plain text size. This is quit a lot. Since all our Xenapp servers are virtualised and provisioned will every reboot remove the changes in the registry. So the package remains very slow. We created a script that runs on all servers and starts a CMD.exe in a virtualised process. This will trigger staging and thus loading the entire registry. When this script is ran before any users connect, users will always have a good performance.

#import AppVClient module
ipmo *appv*

#get all appv packages
$apps = Get-AppvClientPackage
$obj = @()

#Get connection group applications
$connApps = (Get-AppvClientConnectionGroup).GetPackages()

#remove connecion-group applications from the list
[System.Collections.ArrayList]$appList = $apps
foreach ($connApp in $connApps) {$appList = $appList | ? { $_.name -ne $connApp.Name }}


#for each (non-connection group) appv package...
foreach ($app in $appList) {
$prop = New-Object System.Object

#get each appv package ID and version ID
$package = ($app.packageID).ToString()
$version = ($app.versionID).ToString()

#start a blank cmd.exe in the environment (this kicks off the AppV5 registry staging)
Start-AppvVirtualProcess -AppvClientObject (Get-AppvClientPackage $app.name) cmd.exe


    do {
    write-host "Testing: " $app.name
        sleep 1
       }
       until (Test-Path "HKLM:\SOFTWARE\Microsoft\AppV\Client\Packages\$package\Versions\$version\RegistryStagingFinished")
       write-host $app.name
       $appvProcess = get-appvVirtualProcess
       stop-process $appvProcess -force
    }

#for each connection group package...
$conGroups = Get-AppvClientConnectionGroup
foreach ($group in $conGroups) {
$prop = New-Object System.Object

#get each connection group package ID and version ID
$package = ($group.groupID).ToString()
$version = ($group.versionID).ToString()

#start a blank cmd.exe in the environment (this kicks off the AppV5 registry staging)
Start-AppvVirtualProcess -AppvClientObject ($group) cmd.exe


    do {
    write-host "Testing: " $group.name
        sleep 1
       }
       until (Test-Path "HKLM:\SOFTWARE\Microsoft\AppV\Client\PackageGroups\$package\Versions\$version\RegistryStagingFinished")
       write-host $group.name
       $appvProcess = get-appvVirtualProcess
       stop-process $appvProcess -force
    }
    

In order to start the above script I used DPAPI to encrypt the password of the service account. I configured a scheduled task to run every day at 6:30 AM. I wrote a article or two about his some time ago. It is important to encrypt the password when logged in to that account. The profile of that service account should not be removed!

The script used to start the script above is shown below

<#Custom Module#>
Import-Module "LocationWhenTheFunctionIsLocated\Function.psm1" -force
<#Script Start#>
Add-PSSnapin Citrix*
#Note that the username is without the domain name
$UserName = "ServiceAccount"
$SecureString = Get-Content -Path "LocationOfTheStoredPassword.txt" | ConvertTo-SecureString -Entropy ([Math]::PI)
$SecureString = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString))
#If the server that launch the script is not a Citrix server you have to add -ComputerName COMPUTERNAME
$OnlineServers = Get-XAServer -ZoneName FarmName -OnlineOnly  | ?{$_.ServerName -match "PrefixFromProductionServers" -and $_.ServerName -notmatch "tst" -and $_.ServerName -notmatch "DEV"}
#Line below is for testing purposes
Add-content -Value $OnlineServers -Path "G:\Scheduler_Scripts\GMN\App-V Prelaunch\Test.csv"
Foreach ($Server in $Onlineservers){

$ServerName = $Server.ServerName
$Date = (Get-Date).AddMinutes(30)
schtasks /create /S $ServerName /RU DOMAIN\$Username /RP $SecureString /SC Once /SD $date.ToString('dd"/"MM"/"yyy') /ST $date.ToString('HH":"mm') /TN "App-V 5 Prelaunch Script" /TR "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe \\SharedLocationOfScriptAbove.ps1" /F 
}

A few things are important to make this work. First you need to share the first script on a shared location, let’s say the netlogon or whatever directory works for you. Next you’ll need to have the Xenapp SDK installed on the server that runs the script. This is needed. Only the Powershell CMDlets are required. You need to have a Xenapp server that can be reached over the network. You’ll also need to know the farm name. And of course you’ll need the have the function loaded. I added the function below this post. Check you Execution policy settings before scheduling.

Function

Find members of a local group

We needed a list of all local admins in the domain. I found some code somewhere and adjusted it to my personal needs.

Import-Module ActiveDirectory

$date = (get-date).AddDays(-35)
$list = (Get-ADComputer -Filter {(OperatingSystem -like "*Server*") -and (PasswordLastSet -ge $Date)} ).Name

$Result = @()

foreach($server in $list){

$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)

function getAdmins

{$members = $Group.psbase.invoke(”Members”) | %{$_.GetType().InvokeMember(”Adspath”, ‘GetProperty’, $null, $_, $null)}
$members}

$Result += $server
$Result += ( getAdmins )
$Result += " "
}
$Result | ac c:\LocalAdmins.csv

Manage websites from local server

$list =@(Get-WmiObject -Namespace "root/Webadministration" -Query 'Select * from Site')
Foreach ($item in $list){write-host $item.name}

IIS6 compatiblity uses a different namespace root/MicrosoftIISv2

All you need to do is get cred en authenticate to a different server using either -ComputerName $Hostname or  -Authentication “6”.

Next you can create a remote job using the earlier cached credentials and create a script with the listed names like this:

Import-Module WebAdministration
Stop-Website "$name" 
$State = Get-website |Where {$_.Name -eq "$Name"} | Select State
If ($State -notmatch "Stopped"){Stop-Website $Name }

Of course you can also you the schtasks /create command (in Powershell) to add a job that deletes itself or enable-psremoting.

An entire different approach would be to connect using the servermanager via Powershell. You can load the servermanager and connect to a server. The advantage would be that one server is responsible. You do need access to that server. Some high secure environments block this kind of traffic. You can load any dll for IIS Web Administration

[System.Reflection.Assembly]::LoadFrom( "C:\windows\system32\inetsrv\Microsoft.Web.Administration.dll" )
$computer = "COMPUTERNAME"
$ServerManager = [Microsoft.Web.Administration.ServerManager]::OpenRemote($computer.ToLower())
[int]$ID="1"
Foreach ($item in $ServerManager.sites){
Write-host "$ID $item"
$ID++
}

while ($ID -gt $Servermanager.sites.count){
     $ID = read-host "Voer de site die u wilt stoppen"
      $ID--}


$ServerManager.Sites.Item($ID).stop()

Function for securing passwords with DPAPI

DPAPI is a encryptionprofile that prevents any other process to access data. Only your user account can decrypt the data. Also only on the computer where you encrypted to password on. If you have a roaming profile you don’t have this issue. What administrator use a roaming profile!?

PSM1:

requires -Version 2.0

if ($PSVersionTable.PSVersion.Major -ge 3)
{
    Import-Module Microsoft.PowerShell.Security -Global
}

$script:validEntropyTypes = @(
    [System.ValueType]
    [string]
    [System.Security.SecureString]
    [System.Text.StringBuilder]
)

function ConvertTo-SecureString
{
    <#
    
    .ForwardHelpTargetName ConvertTo-SecureString
    .ForwardHelpCategory Cmdlet
    
    #>

    [CmdletBinding(DefaultParameterSetName='Secure')]
    param(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [string]
        ${String},
    
        [Parameter(ParameterSetName='PlainText', Position=1)]
        [switch]
        ${AsPlainText},
    
        [Parameter(ParameterSetName='PlainText', Position=2)]
        [Parameter(ParameterSetName = 'WithEntropy')]
        [switch]
        ${Force},
    
        [Parameter(ParameterSetName='Secure', Position=1)]
        [System.Security.SecureString]
        ${SecureKey},
    
        [Parameter(ParameterSetName='Open')]
        [byte[]]
        ${Key},

        [Parameter(ParameterSetName = 'WithEntropy')]
        [Object]
        $Entropy
    )
    
    begin
    {
        Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

        if ($PSCmdlet.ParameterSetName -eq 'WithEntropy')
        {
            try
            {
                $entropyBytes = Get-EntropyBytes -Entropy $Entropy -Force:$Force
            }
            catch
            {
                throw
            }
        }
    }

    process
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            'WithEntropy'
            {
                Add-Type -AssemblyName 'System.Security' -ErrorAction Stop

                try
                {
                    $encryptedBytes = Get-ByteArrayFromString -String $String
                    $plainTextBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedBytes, $entropyBytes, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
                    $plainTextChars = [System.Text.Encoding]::Unicode.GetChars($plainTextBytes)

                    $secureString = New-Object System.Security.SecureString

                    foreach ($char in $plainTextChars)
                    {
                        $secureString.AppendChar($char)
                    }

                    $secureString.MakeReadOnly()

                    return $secureString
                }
                catch
                {
                    Write-Error -ErrorRecord $_
                }
                finally
                {
                    if ($null -ne $plainTextChars) { [array]::Clear($plainTextChars, 0, $plainTextChars.Count) }
                    if ($null -ne $plainTextBytes) { [array]::Clear($plainTextBytes, 0, $plainTextBytes.Count) }
                    if ($null -ne $entropyBytes)   { [array]::Clear($entropyBytes, 0, $entropyBytes.Count) }
                }
                
                break
            }

            default
            {
                try
                {
                    $cmd = Get-Command -Name ConvertTo-SecureString -CommandType Cmdlet
                    return & $cmd @PSBoundParameters
                }
                catch
                {
                    Write-Error -ErrorRecord $_
                }
                
                break
            }
        }

    } # process
    
} # function ConvertTo-SecureString

function ConvertFrom-SecureString
{
    <#
    
    .ForwardHelpTargetName ConvertFrom-SecureString
    .ForwardHelpCategory Cmdlet
    
    #>

    [CmdletBinding(DefaultParameterSetName='Secure')]
    param(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [ValidateScript({ $_.Length -gt 0 })]
        [System.Security.SecureString]
        ${SecureString},
    
        [Parameter(ParameterSetName='Secure', Position=1)]
        [System.Security.SecureString]
        ${SecureKey},
    
        [Parameter(ParameterSetName='Open')]
        [byte[]]
        ${Key},

        [Parameter(ParameterSetName = 'WithEntropy')]
        [Object]
        $Entropy,

        [Parameter(ParameterSetName = 'PlainText')]
        [switch]
        $AsPlainText,

        [Parameter(ParameterSetName = 'PlainText')]
        [Parameter(ParameterSetName = 'WithEntropy')]
        [switch]
        $Force
    )
    
    begin
    {
        Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

        if ($PSCmdlet.ParameterSetName -eq 'WithEntropy')
        {
            Add-Type -AssemblyName 'System.Security'

            try
            {
                $entropyBytes = Get-EntropyBytes -Entropy $Entropy -Force:$Force
            }
            catch
            {
                throw
            }
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'PlainText' -and -not $Force)
        {
            throw 'The system cannot protect plain text output.  To suppress this warning and convert the SecureString to plain text, reissue the command specifying the Force parameter.'
        }
    }

    process
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            'PlainText'
            {
                try
                {
                    $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($secureString)
                    return [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr)
                }
                catch
                {
                    Write-Error -ErrorRecord $_
                }
                finally
                {
                    if ($null -ne $ptr) { [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr) }
                }
                
                break
            }

            'WithEntropy'
            {
                try
                {
                    $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($secureString)
                    $chars = New-Object Char[]($secureString.Length)
                    [System.Runtime.InteropServices.Marshal]::Copy($ptr, $chars, 0, $secureString.Length)
                    $bytes = [System.Text.Encoding]::Unicode.GetBytes($chars)
                    $encryptedBytes = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $entropyBytes, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)

                    return Get-StringFromByteArray -ByteArray $encryptedBytes
                }
                catch
                {
                    Write-Error -ErrorRecord $_
                }
                finally
                {
                    if ($null -ne $ptr)          { [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr) }
                    if ($null -ne $chars)        { [array]::Clear($chars, 0, $chars.Count) }
                    if ($null -ne $bytes)        { [array]::Clear($bytes, 0, $bytes.Count) }
                    if ($null -ne $entropyBytes) { [array]::Clear($entropyBytes, 0, $entropyBytes.Count) }
                }
                
                break
            }

            default
            {
                try
                {
                    $cmd = Get-Command -Name ConvertFrom-SecureString -CommandType Cmdlet
                    return & $cmd @PSBoundParameters
                }
                catch
                {
                    Write-Error -ErrorRecord $_
                }
                
                break
            }
        }

    } # process

} # function ConvertTo-SecureString

function Get-ByteArrayFromString
{
    # Converts a string containing an even number of hexadecimal characters into a byte array.

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateScript({
            # Could use ValidatePattern for this, but ValidateScript allows for a more user-friendly error message.
            if ($_ -match '[^0-9A-F]')
            {
                throw 'String must only contain hexadecimal characters (0-9 and A-F).'
            }

            if ($_.Length % 2 -ne 0)
            {
                throw 'String must contain an even number of characters'
            }

            return $true
        })]
        [string]
        $String
    )

    $length = $String.Length / 2
    $bytes = New-Object byte[]($length)

    for ($i = 0; $i -lt $length; $i++)
    {
        $bytes[$i] = [byte]::Parse($String.Substring($i * 2, 2), [Globalization.NumberStyles]::AllowHexSpecifier, [Globalization.CultureInfo]::InvariantCulture)
    }

    return ,$bytes
}

function Get-EntropyBytes
{
    [CmdletBinding()]
    param (
        $Entropy,

        [switch]
        $Force
    )

    if ($null -eq $Entropy)
    {
        return $null
    }
    elseif (-not $Force -and -not (Test-EntropyType -Object $Entropy))
    {
        throw @'
Entropy object's type should be a value type, string, StringBuilder, SecureString, or an array containing only value types or strings.
Use of other object types might result in a different binary representation of the object between script executions.  To suppress this message and use any type of entropy object, use the -Force switch.
'@
    }

    if ($Entropy -is [byte[]])
    {
        # Clone the object because the caller may be zeroing out the byte array when they're done, due
        # to it potentially containing plain text data from a SecureString.

        return $Entropy.Clone()
    }
    elseif ($Entropy -is [string])
    {
        return [System.Text.Encoding]::Unicode.GetBytes($Entropy)
    }
    elseif ($Entropy -is [System.Text.StringBuilder])
    {
        return [System.Text.Encoding]::Unicode.GetBytes($Entropy.ToString())
    }
    elseif ($Entropy -is [System.Security.SecureString])
    {
        try
        {
            $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($Entropy)
            $chars = New-Object Char[]($Entropy.Length)

            [System.Runtime.InteropServices.Marshal]::Copy($ptr, $chars, $Entropy.Length)

            return [System.Text.Encoding]::Unicode.GetBytes($chars)
        }
        catch
        {
            throw
        }
        finally
        {
            if ($null -ne $ptr) { [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr) }
            if ($null -ne $chars) { [Array]::Clear($chars) }
        }
    }
    else
    {
        try
        {
            $ms = New-Object System.IO.MemoryStream
            $bf = New-Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter

            $bf.Serialize($ms, $Entropy)
            
            return ,$ms.ToArray()
        }
        catch
        {
            throw
        }
        finally
        {
            if ($null -ne $ms) { $ms.Dispose() }
        }
    }
}

function Get-StringFromByteArray
{
    # Converts byte array into a string of hexadecimal characters in the same order as the byte array

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [byte[]]
        $ByteArray
    )

    $sb = New-Object System.Text.StringBuilder

    for ($i = 0; $i -lt $ByteArray.Length; $i++)
    {
        $null = $sb.Append($ByteArray[$i].ToString('x2', [Globalization.CultureInfo]::InvariantCulture))
    }

    return $sb.ToString()
}

function Test-EntropyType
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        $Object
    )

    if ($Object -is [array])
    {
        foreach ($obj in $Object)
        {
            if ($obj -isnot [System.ValueType] -and $obj -isnot [string])
            {
                return $false
            }
        }

        return $true
    }
    else
    {
        foreach ($type in $script:validEntropyTypes)
        {
            if ($Object -is $type)
            {
                return $true
            }
        }

        return $false
    }
}

function Get-CallerPreference
{
    <#
    .Synopsis
       Fetches "Preference" variable values from the caller's scope.
    .DESCRIPTION
       Script module functions do not automatically inherit their caller's variables, but they can be
       obtained through the $PSCmdlet variable in Advanced Functions.  This function is a helper function
       for any script module Advanced Function; by passing in the values of $ExecutionContext.SessionState
       and $PSCmdlet, Get-CallerPreference will set the caller's preference variables locally.
    .PARAMETER Cmdlet
       The $PSCmdlet object from a script module Advanced Function.
    .PARAMETER SessionState
       The $ExecutionContext.SessionState object from a script module Advanced Function.  This is how the
       Get-CallerPreference function sets variables in its callers' scope, even if that caller is in a different
       script module.
    .PARAMETER Name
       Optional array of parameter names to retrieve from the caller's scope.  Default is to retrieve all
       Preference variables as defined in the about_Preference_Variables help file (as of PowerShell 4.0)
       This parameter may also specify names of variables that are not in the about_Preference_Variables
       help file, and the function will retrieve and set those as well.
    .EXAMPLE
       Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

       Imports the default PowerShell preference variables from the caller into the local scope.
    .EXAMPLE
       Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState -Name 'ErrorActionPreference','SomeOtherVariable'

       Imports only the ErrorActionPreference and SomeOtherVariable variables into the local scope.
    .EXAMPLE
       'ErrorActionPreference','SomeOtherVariable' | Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

       Same as Example 2, but sends variable names to the Name parameter via pipeline input.
    .INPUTS
       String
    .OUTPUTS
       None.  This function does not produce pipeline output.
    .LINK
       about_Preference_Variables
    #>

    [CmdletBinding(DefaultParameterSetName = 'AllVariables')]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateScript({ $_.GetType().FullName -eq 'System.Management.Automation.PSScriptCmdlet' })]
        $Cmdlet,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.SessionState]
        $SessionState,

        [Parameter(ParameterSetName = 'Filtered', ValueFromPipeline = $true)]
        [string[]]
        $Name
    )

    begin
    {
        $filterHash = @{}
    }
    
    process
    {
        if ($null -ne $Name)
        {
            foreach ($string in $Name)
            {
                $filterHash[$string] = $true
            }
        }
    }

    end
    {
        # List of preference variables taken from the about_Preference_Variables help file in PowerShell version 4.0

        $vars = @{
            'ErrorView' = $null
            'FormatEnumerationLimit' = $null
            'LogCommandHealthEvent' = $null
            'LogCommandLifecycleEvent' = $null
            'LogEngineHealthEvent' = $null
            'LogEngineLifecycleEvent' = $null
            'LogProviderHealthEvent' = $null
            'LogProviderLifecycleEvent' = $null
            'MaximumAliasCount' = $null
            'MaximumDriveCount' = $null
            'MaximumErrorCount' = $null
            'MaximumFunctionCount' = $null
            'MaximumHistoryCount' = $null
            'MaximumVariableCount' = $null
            'OFS' = $null
            'OutputEncoding' = $null
            'ProgressPreference' = $null
            'PSDefaultParameterValues' = $null
            'PSEmailServer' = $null
            'PSModuleAutoLoadingPreference' = $null
            'PSSessionApplicationName' = $null
            'PSSessionConfigurationName' = $null
            'PSSessionOption' = $null

            'ErrorActionPreference' = 'ErrorAction'
            'DebugPreference' = 'Debug'
            'ConfirmPreference' = 'Confirm'
            'WhatIfPreference' = 'WhatIf'
            'VerbosePreference' = 'Verbose'
            'WarningPreference' = 'WarningAction'
        }


        foreach ($entry in $vars.GetEnumerator())
        {
            if (([string]::IsNullOrEmpty($entry.Value) -or -not $Cmdlet.MyInvocation.BoundParameters.ContainsKey($entry.Value)) -and
                ($PSCmdlet.ParameterSetName -eq 'AllVariables' -or $filterHash.ContainsKey($entry.Name)))
            {
                $variable = $Cmdlet.SessionState.PSVariable.Get($entry.Key)
                
                if ($null -ne $variable)
                {
                    if ($SessionState -eq $ExecutionContext.SessionState)
                    {
                        Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force
                    }
                    else
                    {
                        $SessionState.PSVariable.Set($variable.Name, $variable.Value)
                    }
                }
            }
        }

        if ($PSCmdlet.ParameterSetName -eq 'Filtered')
        {
            foreach ($varName in $filterHash.Keys)
            {
                if (-not $vars.ContainsKey($varName))
                {
                    $variable = $Cmdlet.SessionState.PSVariable.Get($varName)
                
                    if ($null -ne $variable)
                    {
                        if ($SessionState -eq $ExecutionContext.SessionState)
                        {
                            Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force
                        }
                        else
                        {
                            $SessionState.PSVariable.Set($variable.Name, $variable.Value)
                        }
                    }
                }
            }
        }

    } # end

} # function Get-CallerPreference


Export-ModuleMember -Function 'ConvertTo-SecureString', 'ConvertFrom-SecureString', 'Get-CallerPreference'

ZIP:

ConvertTo-SecureFunctions