Create custom Nano server 2016 VHD

Create nano server

I built this script so people easily can get a start and try their Nano server 2016 within a couple of minutes. I have only tried it with Windows server 2016 Technical preview 4. It is pretty straight forward the script launches a windows where you can name your server and check the functions you want, add the disk image och create the server:

1. Enter namne of VHD
2. Check the functions you want to add and press button add server, you can add multiple server if you want to.
3. Press button Add Disk Image to point out windows 2016 iso.
4. Press button create servers to start building VHDs.
5. Enter Administrator password
6. You ne VHD will be stored in C:\Temp_NanoServer

I have not tried all functions, hopefully they all work.

########################################################################################################
#Name: Create Nano Vhd.ps1
#Author: Viktor Lindström
#
#Comments:
#Creates custom Nano VHD from Windows 2016 ISO. I have only tried it with Technical preview 4.
#You have to run the script as administrator.
#Sometimes the script stop on "dismounting image..." this is microsofts script that stops and the vhd is created att you can stop the script manually.
#1. Enter namne of VHD
#2. Check the functions you want to add and press button add server, you can add multiple server if you want to.
#3. Press button Add Disk Image to point out windows 2016 iso.
#4. Press button create servers to start building VHDs.
#5. Enter Administrator password
#6. You ne VHD will be stored in C:\Temp_NanoServer
########################################################################################################




#build GUI


[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')

[xml]$XAML = @'

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:create_nano_server"
        Title="Create Nano Server" Height="483.193" Width="705.901">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="133*"/>
            <RowDefinition Height="319*"/>
        </Grid.RowDefinitions>
        <Label Name="labelComputername" Content="Computer name:" HorizontalAlignment="Left" Margin="24,28,0,0" VerticalAlignment="Top" Width="111" Height="26"/>
        <Label Name="labelFeaturesAndRoles" Content="Feature and roles:" HorizontalAlignment="Left" Margin="24,73,0,0" VerticalAlignment="Top" FontWeight="Bold" Height="26" Width="111"/>
        <CheckBox Name="checkBoxHyper" Content="Hyper-V role" HorizontalAlignment="Left" Margin="33,108,0,0" VerticalAlignment="Top" Height="15" Width="88"/>
        <CheckBox Name="checkBoxFailover" Content="Failover Clustering" HorizontalAlignment="Left" Margin="33,128,0,0" VerticalAlignment="Top" Height="15" Width="117" Grid.RowSpan="2"/>
        <CheckBox Name="checkBoxFile" Content="File Server role and other storage components" HorizontalAlignment="Left" Margin="33,35,0,0" VerticalAlignment="Top" Grid.Row="1" Height="15" Width="265"/>
        <CheckBox Name="checkBoxWindowsDefender" Content="Windows Defender Antimalware" HorizontalAlignment="Left" Margin="33,54,0,0" VerticalAlignment="Top" Grid.Row="1" Height="15" Width="190"/>
        <CheckBox Name="checkBoxReverse" Content="Reverse forwarders for application compatibility" HorizontalAlignment="Left" Margin="33,75,0,0" VerticalAlignment="Top" Grid.Row="1" Height="15" Width="272"/>
        <CheckBox Name="checkBoxDns" Content="DNS Server role" HorizontalAlignment="Left" Margin="33,95,0,0" VerticalAlignment="Top" Grid.Row="1" Height="15" Width="104"/>
        <CheckBox Name="checkBoxDcs" Content="Desired State Configuration (DSC)" HorizontalAlignment="Left" Margin="33,115,0,0" VerticalAlignment="Top" Grid.Row="1" Height="15" Width="199"/>
        <CheckBox Name="checkBoxIis" Content="Internet Information Server (IIS)" HorizontalAlignment="Left" Margin="33,135,0,0" VerticalAlignment="Top" Grid.Row="1" Height="15" Width="187"/>
        <CheckBox Name="checkBoxContainers" Content="Host support for Windows Containers" HorizontalAlignment="Left" Margin="33,155,0,0" VerticalAlignment="Top" Grid.Row="1" Height="15" Width="220"/>
        <CheckBox Name="checkBoxSystemCenter" Content="System Center Virtual Machine Manager agent" HorizontalAlignment="Left" Margin="33,175,0,0" VerticalAlignment="Top" Grid.Row="1" Height="15" Width="266"/>
        <CheckBox Name="checkBoxNpds" Content="Network Performance Diagnostics Service (NPDS)" HorizontalAlignment="Left" Margin="33,195,0,0" VerticalAlignment="Top" Grid.Row="1" Height="15" Width="281"/>
        <CheckBox Name="checkBoxDcb" Content="Data Center Bridging" HorizontalAlignment="Left" Margin="33,15,0,0" VerticalAlignment="Top" Grid.Row="1" Height="15" Width="389"/>
        <TextBox Name="textBoxComputerName" HorizontalAlignment="Left" Height="23" Margin="133,32,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
        <Label Name="labelServers" Content="Servers:" HorizontalAlignment="Left" Margin="382,28,0,0" VerticalAlignment="Top" Height="26" Width="51"/>
        <ListBox Name="listBox" HorizontalAlignment="Left" Height="332" Margin="456,32,0,0" Grid.RowSpan="2" VerticalAlignment="Top" Width="227"/>
        <Button Name="buttonAddServer" Content="Add Server" HorizontalAlignment="Left" Margin="33,247,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" Height="20"/>
        <Button Name="buttonCreateServers" Content="Create Servers" HorizontalAlignment="Left" Margin="456,276,0,0" Grid.Row="1" VerticalAlignment="Top" Width="99" Height="20"/>
        <Button Name="buttonAddIso" Content="Add Disk Image" HorizontalAlignment="Left" Margin="456,247,0,0" Grid.Row="1" VerticalAlignment="Top" Width="99" RenderTransformOrigin="0.5,0.5"/>
        <Label Name="labelAddDiskImage" Content="" HorizontalAlignment="Left" Margin="107,244,0,0" Grid.Row="1" VerticalAlignment="Top" Width="312"/>

    </Grid>
</Window>
'@

$reader=(New-Object System.Xml.XmlNodeReader $xaml) 
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader. Some possible causes for this problem include: .NET Framework is missing PowerShell must be launched with PowerShell -sta, invalid XAML code was encountered."; exit}
$xaml.SelectNodes("//*[@Name]") | foreach {Set-Variable -Name ($_.Name) -Value $Form.FindName($_.Name)}


############################################################################################################
$tempPath = "C:\Temp_NanoServer"
$hyperV = "-Compute "
$clustering = "-Clustering "
$oemDrivers = "-OEMDrivers "
$fileServer = "-Storage "
$defender = "-Defender "
$reverseForwarders = "-ReverseForwarders "
$dnsServer = "-Packages Microsoft-NanoServer-DNS-Package "
$dsc = "-Packages Microsoft-NanoServer-DSC-Package "
$iis = "-Packages Microsoft-NanoServer-IIS-Package "
$containers = "-Containers "
$sccm = "-Packages Microsoft-Windows-Server-SCVMM-Package -Packages Microsoft-Windows-Server-SCVMM-Compute-Package " 
$npds = "-Packages Microsoft-NanoServer-NPDS-Package "
$dcb = "-Packages Microsoft-NanoServer-DCB-Package "
$ipAddress = "-Ipv4Address "
$subnetMask = "-Ipv4SubnetMask "
$gateway = "-Ipv4Gateway "
$azure = "-ForAzure "




# Show an Open File Dialog and return the file selected by the user.
function Read-OpenFileDialog([string]$WindowTitle, [string]$InitialDirectory, [string]$Filter = "All files (*.*)|*.*", [switch]$AllowMultiSelect)
{  
    Add-Type -AssemblyName System.Windows.Forms
    $openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
    $openFileDialog.Title = $WindowTitle
    if (![string]::IsNullOrWhiteSpace($InitialDirectory)) { $openFileDialog.InitialDirectory = $InitialDirectory }
    $openFileDialog.Filter = $Filter
    if ($AllowMultiSelect) { $openFileDialog.MultiSelect = $true }
    $openFileDialog.ShowHelp = $true    # Without this line the ShowDialog() function may hang depending on system configuration and running from console vs. ISE.
    $openFileDialog.ShowDialog() > $null
    if ($AllowMultiSelect) { return $openFileDialog.Filenames } else { return $openFileDialog.Filename }
}


$servers = @()

        $buttonAddServer.add_click({ 

        if ($checkBoxHyper.IsChecked) {$arguments += $hyperV}
        if ($checkBoxFailover.IsChecked) {$arguments += $clustering}
        if ($checkBoxBasic.IsChecked) {$arguments += $oemDrivers}
        if ($checkBoxFile.IsChecked) {$arguments += $fileServer}
        if ($checkBoxWindowsDefender.IsChecked) {$arguments += $defender}
        if ($checkBoxReverse.IsChecked) {$arguments += $reverseForwarders}
        if ($checkBoxDns.IsChecked) {$arguments += $dnsServer}
        if ($checkBoxDcs.IsChecked) {$arguments += $dsc}
        if ($checkBoxIis.IsChecked) {$arguments += $iis}
        if ($checkBoxContainers.IsChecked) {$arguments += $containers}
        if ($checkBoxSystemCenter.IsChecked) {$arguments += $sccm}
        if ($checkBoxNpds.IsChecked) {$arguments += $npds}
        if ($checkBoxDcb.IsChecked) {$arguments += $dcb}

            $Global:servers += New-Object psobject -Property @{
                        'Servername' = $textBoxComputerName.Text
                        'arguments' = $arguments
                        }
                        
                        $listServerArguments = $textBoxComputerName.Text + " " + $arguments
                        $listBox.Items.Add($listServerArguments)
                       if ($Variable)
                        {
                        Clear-Variable -Name arguments
                        }
})
            $buttonAddIso.add_click({

            $Global:imagePath = Read-OpenFileDialog -WindowTitle "Select Windows 2016 ISO" -InitialDirectory 'C:\' -Filter "ISO files (*.iso)|*.iso"
            if (![string]::IsNullOrEmpty($imagePath)) { $labelAddDiskImage.Content = $imagePath }
            else { "You did not select a file." }
           
            
            })
                
                $buttonCreateServers.add_click({

                    # Mount disk image.
                    Mount-DiskImage -ImagePath $imagePath 
                    
                    # Get driveletter of mounted diskimage.
                    $driveLetterIso = (Get-DiskImage -ImagePath $imagePath | Get-Volume).DriveLetter + ":\"
                    
                    # Path to Nano Server folder on image.
                    $pathNanoServer = $driveLetterIso + "NanoServer"

                    
                    if (-Not(Test-Path "C:\temp_NanoServer\Convert-WindowsImage.ps1"))
                    {
                    # Create temp path to place scripts from nanoserverfolder.
                    New-Item -ItemType Directory -Path $tempPath
                    # Copy scripts from disk image to temp folder.
                    Copy-Item -Path ($pathNanoServer + "\*.ps*") -Destination $tempPath
                    }
                        
                        # Test if user is running powershell as administrator
                        function Test-Administrator  
                            {  
                            $user = [Security.Principal.WindowsIdentity]::GetCurrent();
                            (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)  
                            }

                                if (Test-Administrator)
                                {
                                    Set-Location $tempPath
                                    Import-Module .\NanoServerImageGenerator.psm1 -Verbose
                                  
                                }
                                else
                                {
                                Write-Host "You are not running powershell as administrator" -ErrorAction Stop -ForegroundColor Red -BackgroundColor Yellow
                                $labelAddDiskImage.Content = "You are not running powershell as administrator"
                                return
                                }
                                
            foreach ($server in $servers)
            {
                # create the VHD
                $targetPath = ".\nanoServerVM\" +$server.Servername + ".vhd"
                $commandNewNanoServer = "New-NanoServerImage -MediaPath $driveLetterIso -BasePath .\Base -TargetPath $targetPath -ComputerName $($server.Servername) $($server.arguments) -GuestDrivers -EnableRemoteManagementPort -Language en-US"
                Invoke-Expression -Command $commandNewNanoServer
          
            }
             })

$Form.ShowDialog() | out-null

Check which servers have powershell remote enable

Sometimes you need to run a Powershell remote script on a big number of servers or computers. Powershell remoting can often fail and it can be all kinds of reasons of failing, powershell remoting is disabled, a firewall or two is blocking the connection or you don’t have the permission on the remote computer to enter a powershell remote session. I ended up in one of this scenarios the other day were I had local administrator permission on all the servers and an open firewall but still couldn’t enter a remote session on a bunch of servers. I put together this simple script that first collects all of our servers from the Active Directory and then creates an empty array to collect the result from the foreach-loop later in the script:

$Servername = Get-ADComputer -Filter 'Name -like "srv*" -or Name -like "poc*" -or Name -like "test*"'
$serversNoPsr = @()

After that I take the result from the Get-ADComputers and and crunch it through a foreach-loop where I try to enter powershell remote sesssion with all the servers that I collected in the previous step . I use the ErrorAction parameter with the stop switch to get an terminating error. When I get an terminating error that is when Enter-PSSession fail, the code inside the catch braces will run and there I just add the server name to the array I created earlier:

foreach ($server in $servername.name)
{
try
{
    Write-Host "checking $server"
    Enter-PSSession -ComputerName $server -ErrorAction Stop
    exit-pssession
}
catch 
{
  $serversNoPsr += $server
}

When the script is done I will end up with all the failed servers in the $serversNoPsr variable.

Full script:

$Servername = Get-ADComputer -Filter 'Name -like "srv*" -or Name -like "poc*" -or Name -like "test*"'
$serversNoPsr = @()

foreach ($server in $servername.name)
{
try
{
    Write-Host "checking $server"
    Enter-PSSession -ComputerName $server -ErrorAction Stop
    exit-pssession
}
catch 
{
  $serversNoPsr += $server
}    
}

Exchange get all users and list names and which database they are located on

At work we sometimes get restore cases on mailboxes where the backup is located on tapes outside of the tape robot. We the have to restore all databases and then manually find which database the user is located on. This is very time consuming and to cut restore time and to make it easier for the technician doing the restore I came up with this script that will export which database the user is located on the specific date. with this information the technician will only have to restore a single database. The script will mail the result as an attachment to one of our function mailboxes and store it locally on the server where the script will run as a scheduled task.

# Filepath with uniqe date.
$date = get-date -Format yyyy.MM.dd
$result = "C:\backup\Resultat_" + $date +".txt"

# Get all mailboxes in Exchange, sort after database and name, select name and database and then export to text file.
Get-Mailbox -Resultsize Unlimited | Sort-Object database, name | Select-Object name, displayname, database | Export-csv $result  -NoTypeInformation

#Mail the text file to mail@domain.se
Send-MailMessage -Attachments $result -Subject "Users and Databases Exchange" -Body "Bifogad fil innehaller vilka anvandare som ligger pa vilken databas för tillfallet i Exchange" -To "mail@domain.se" -From "backup.exchange@daomain.se" -SmtpServer "smtp.server@domain.se" 

Progress bar Example

progress_bar

 

 

When working with large loops that take a lot of time it’s nice to know that something happens and the script is making progress. You can use write-host $item to write something that is  unique in the loop or even better use the count method on the variable that you have stored the data you want to loop and then use an integral to count up. if you do that you can easily use the write-progress cmdlet to get a nice progress bar.

I often end up working with foreach  loops and here is an example how to use the write-progress in an foreach loop. In this example I use the status-parameter to get the xxx services out of 235 is done counter and the PercentComplete-paratmeter. I use an integral that I set to 0 in the beginning and then ad 1 in each foreach loop,in that way I can easily track how many of the 235 items that I have looped through. To get the percentage I need to divide the int every time with the total I got from the count method, in this case 235 and then multiply it with 100 to get the percent.

 

$service = Get-Service
$i = 0
$int = $service.Count 


foreach ($item in $service)
{ 
    Write-Host $item
    $i++
    Write-Progress -Activity "List all services" -PercentComplete ($i / $service.Count*100) -Status "$i services out of $int is done"
   
  
}

First help desk GUI tool

helpdeskDelivered my first GUI tool to our help desk today. They needed a tool to find out which users populated distribution groups and they also wanted to be able to export three different attributes to a csv. I did the GUI design in visual studio and used XAML because it looks a little nicer then the old windows forms. the application got real-time check against the Active directory as you type to check if the group exist and it doesn´t enable the list user(Lista användare) button until you type the name of a group that exist. When the listing is done you can choose which attributes to export. This is a 1.0 and it doesn’t handle nested groups, if they want that functionality or some other functionality in the future I will update this post.

 

<#

.SYNOPSIS

Få reda på och exportera vilka medlemmar en AD-grupp har.



.DESCRIPTION

Kör skriptet och fyll grupp, applikationen kollar så att gruppen finns i AD:et annars går det inte att söka. Vid export bockar man i vilka attribut 
man vill exportera och trycker på exportknappen, det skapas då en fil under c:\temp som heter resultat[timme.minut.sekund].csv tex c:\temp\resultat10.30.21.csv.

.NOTES
Made by: Viktor Lindström
http://powerhell.nu
Version 1.0 fyll på med förändringar här under.
#>

#build GUI

[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')

[xml]$XAML = @'

<Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Medlemmar i AD-grupp" Height="350" Width="687.807">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="258*"/>
            <ColumnDefinition Width="259*"/>
        </Grid.ColumnDefinitions>
        <TextBox Name="textBox" HorizontalAlignment="Left" Height="23" Margin="99,28,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="230"/>
        <Label Name="LabelGruppnamn" Content="Gruppnamn:" HorizontalAlignment="Left" Margin="10,25,0,0" VerticalAlignment="Top" Width="76" RenderTransformOrigin="0.005,0.483"/>
        <ListBox Name="ListboxResult" Grid.Column="1" HorizontalAlignment="Left" Height="202" Margin="32,70,0,0" VerticalAlignment="Top" Width="279"/>
        <Button Name="ButtonSearch" Content="Lista användare" HorizontalAlignment="Left" Margin="99,70,0,0" VerticalAlignment="Top" Width="230" Height="35"/>
        <Label Name="LabelAnvändare" Content="Användare i gruppen:" Grid.Column="1" HorizontalAlignment="Left" Margin="185,25,0,0" VerticalAlignment="Top" Width="126"/>
        <CheckBox Name="checkBoxAnvändarnamn" Content="Användarnamn" HorizontalAlignment="Left" Margin="99,143,0,0" VerticalAlignment="Top" Width="240"/>
        <CheckBox Name="checkBoxNamn" Content="Namn" HorizontalAlignment="Left" Margin="99,169,0,0" VerticalAlignment="Top" Width="240"/>
        <CheckBox Name="checkBoxMail" Content="E-mailadress" HorizontalAlignment="Left" Margin="99,195,0,0" VerticalAlignment="Top" Width="240"/>
        <Label Name="labelExport" Content="Vad ska exporteras?" HorizontalAlignment="Left" Margin="10,117,0,0" VerticalAlignment="Top" Width="116"/>
        <Button Name="buttonExport" Content="Exportera" HorizontalAlignment="Left" Margin="99,230,0,0" VerticalAlignment="Top" Width="230" Height="31"/>
        <CheckBox Name="checkboxGruppnamn" Content="Grupp finns?" Grid.Column="1" HorizontalAlignment="Left" Margin="32,31,0,0" VerticalAlignment="Top"/>
        <Label Name="labelExport1" Content="" HorizontalAlignment="Left" Margin="10,279,0,0" VerticalAlignment="Top" Width="319"/>

    </Grid>
</Window>


'@

$reader=(New-Object System.Xml.XmlNodeReader $xaml) 
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader. Some possible causes for this problem include: .NET Framework is missing PowerShell must be launched with PowerShell -sta, invalid XAML code was encountered."; exit}
$xaml.SelectNodes("//*[@Name]") | foreach {Set-Variable -Name ($_.Name) -Value $Form.FindName($_.Name)}

# Global variable for result
$resultArray = @()

# Disable button by default
$ButtonSearch.IsEnabled = $false


# Funtion to test if AD-group exists.
function Test-Group 
{

if ((Get-ADGroup -Filter {SamAccountName -eq $textBox.Text}) -ne $null)
{$checkboxGruppnamn.IsChecked = $true
  $ButtonSearch.IsEnabled = $true} else {$checkboxGruppnamn.IsChecked = $false
  $ButtonSearch.IsEnabled = $false}
    }

# Searchbutton, gets all memebers of the group specified in the textbox, puts result in $resultarray
$ButtonSearch.add_click({
    $script:resultArray = @()
    $ListboxResult.Items.Clear()
    $allMembers = Get-ADGroupMember -Identity $textBox.Text
    $members = $allMembers | Where-Object {$_.objectClass -eq "user"}
    $groups = $allMembers | Where-Object {$_.objectClass -eq "group"}

    
        foreach ($item in $members)
        {
         $member = Get-ADObject -Identity $item -Properties mail, displayname, samaccountname | Select-Object samaccountname, mail, displayname
         $ListboxResult.Items.Add($member.displayname)
         $script:resultArray += $member
        }
          foreach ($group in $groups)
          {
              $ListboxResult.Items.Add($group.name)
              $script:resultArray += $group | Select-Object name
          }
                        })

# Exportbutton, exports attributes that has been checked in the checkboxes, exports to an CSV-file c:\temp\resultat[HH.mm.ss].csv to uniq it.
                $buttonExport.add_click({
                $export = @()
                
                $date = get-date -Format HH.mm.ss
                $exportdate =  "c:\temp\resultat$date.csv"
                if (Test-Path $exportdate) {Remove-Item $exportdate}

                    foreach ($object in $resultArray)
                    {
                     $export += New-Object psobject -Property @{
               
                      "Användarnamn" = if ($checkBoxAnvändarnamn.IsChecked -eq $true) {$object.samaccountname};
                      "Namn" = if ($checkBoxNamn.IsChecked -eq $true) {$object.displayname};
                      "E-mailadress" = if ($checkBoxMail.IsChecked -eq $true) {$object.mail};
                    }
                    }

                    $export | Export-Csv $exportdate -NoTypeInformation -Encoding UTF8 -Delimiter ";"
                    $labelExport1.Content = "Export klar, filen hittar du här: $exportdate"
                    

                    
                 })
# Runs the AD-test group function in realtime as you write in the textbox.
$textBox.add_Keyup({ 
    
    Test-Group
})

$Form.ShowDialog() | out-null

Measure max log-size on multiple servers

I got a little fuzzy case of how much disk all our server event-logs consume. Since the purchaser is still on vacation and couldn’t answer any questions I put together a 0.1 version with the old get-eventlog commandlet, depending on meeting with the purchaser I might replace it with the get-winevent that is a little more powerfull. The text displayed to the user is in Swedish since I´m working against swedes. First the script collects all servers in the active directory. After that I built the function get-logs that first collect all event-logs and then calculates the size of all logs and add the size and servername in an pscustomobject variable, it also adds the servers log size to the global variable $total to get a total size from all servers. I then use the function in an foreach loop with all the collected servers as targets. In the end it send a pop-up window to the user asking if he/she wants to export the result to an CSV-file. If you are interested in what the Swedish sentences means I recommend http://translate.google.com

#Global variables
[double]$result=""
$computerarray = @()
[int]$total = ""

#Array of computers
$servers1 = Get-ADComputer -Filter * -SearchBase "OU=Servers,DC=your,DC=domain,DC=here"
$servers2 = Get-ADComputer -Filter * -SearchBase "OU=Server,OU=Data,DC=your,DC=domain,DC=here"
$DCs = Get-ADComputer -Filter * -SearchBase  "OU=Domain Controllers,DC=your,DC=domain,DC=here"

$computers = $servers1 + $servers2 + $DCs


#function to collect all eventlogs and check max size and fill 
function get-logs 

{ 
param( $computer = $env:COMPUTERNAME
)


$logs = Get-EventLog -ComputerName $computer -LogName * | select *

[int]$Mbytes = ""

foreach ($log in $logs)
{$Mbytes += $log.maximumkilobytes
    
}
$global:result = $Mbytes * 1024 /1MB
$global:total += $result

$script:computerarray += New-Object psobject -Property @{
               
                      Datornamn = $computer
                      Loggar_i_MB = $result -as [int]
                      }
                      }


# Use function get-logs on all computers in array.
foreach ($computer in $computers.name)
{
   Write-Host "kollar $computer"
   get-logs -computer $computer
   #$computer.name
}


# Add total size to result array.
$script:computerarray += New-Object psobject -Property @{
               
                      Datornamn = "Total"
                      Loggar_i_MB = $total
                      }

# Export to CSV
$title = "Exportera till CSV"
$message = "Vill du exportera reslutatet till en CSV fil(C:\temp\comp.csv)?"

$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Ja"

$no = New-Object System.Management.Automation.Host.ChoiceDescription "&Nej"

$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

$result = $host.ui.PromptForChoice($title, $message, $options, 0) 

switch ($result)
    {
        0 {$computerarray | select Datornamn, Loggar_i_MB | Export-Csv C:\temp\comp.csv -NoTypeInformation}
        1 {"OK, allt är klart"}
    }                      

Change windows theme based on season

A couple of Sundays ago I was laying in the couch with a small hangover and my kids were watching cartoons I got an idea that I would like to have my theme and desktop wallpaper change with the weather season. Since I am Swedish I wanted the wallpapers to be from my home country. I found out that Microsoft had season themes with desktop pictures from Sweden on their homepage, so I could easily use Bits to download the themes. I made 5 variables with the days of each weather season stored in them and then used a switch to install the correct theme based on season. It worked like a charm and I scheduled a task on my main computer on each login so it will change theme as when season change. I also sync themes on my other computers that use Windows 8.1 and 10 so i get the correct season theme on all my computers.

$Ssu_theme = "SwedishSummer.themepack"
$Ssp_theme = "SwedishSpring.themepack"
$Win_theme = "SwedishWinter.themepack"
$Aut_theme = "SwedishAutumn.themepack"

$Ssu = "http://download.microsoft.com/download/A/7/4/A7448DBD-5723-44C1-BBB1-51FB285DEB0E/" + $Ssu_theme
$Ssp = "http://download.microsoft.com/download/A/7/4/A7448DBD-5723-44C1-BBB1-51FB285DEB0E/" + $Ssp_theme
$Win = "http://download.microsoft.com/download/A/7/4/A7448DBD-5723-44C1-BBB1-51FB285DEB0E/" + $Win_theme
$Aut = "http://download.microsoft.com/download/A/7/4/A7448DBD-5723-44C1-BBB1-51FB285DEB0E/" + $Aut_theme


$reg_path = "HKCU:\Software\Microsoft\Windows\CurrentVersion\themes"
$reg = Get-ItemProperty -Path $reg_path

$Ssu_theme_reg = "Swedish S\Swedish S.theme"
$Ssp_theme_reg = "Swedish S (2)\Swedish S.theme"
$win_theme_reg = "Swedish W\Swedish W.theme"
$Aut_theme_reg = "Swedish A\Swedish A.theme"



$summer = 152..243
$spring = 60..151
$winter1 = 335..365
$winter2 = 1..59
$winter = $winter1 + $winter2
$Autum = 244..334

$date = get-date

switch ($date.DayOfYear)
{
    {$_ -in $summer} 
    {
    if ($reg.CurrentTheme -notlike "*$Ssu_theme_reg*")
{ if (Test-Path c:\temp\$Ssu_theme)
  {   & c:\temp\$Ssu_theme
  Start-Sleep -Seconds 2
$shell = New-Object -ComObject WScript.Shell; $shell.AppActivate('Personalization'); $shell.SendKeys('%{F4}')

      
  }
  else
  {Start-BitsTransfer $Ssu -Destination c:\temp\$Ssu_theme
  & c:\temp\$Ssu_theme
  Start-Sleep -Seconds 2
$shell = New-Object -ComObject WScript.Shell; $shell.AppActivate('Personalization'); $shell.SendKeys('%{F4}')
}
  }
    }
    {$_ -in $spring} {
    if ($reg.CurrentTheme -notlike "*$Ssp_theme_reg*")
{ if (Test-Path c:\temp\$Ssp_theme)
  {   & c:\temp\$Ssp_theme
  Start-Sleep -Seconds 2
$shell = New-Object -ComObject WScript.Shell; $shell.AppActivate('Personalization'); $shell.SendKeys('%{F4}')

      
  }
  else
  {Start-BitsTransfer $Ssp -Destination c:\temp\$Ssp_theme
  & c:\temp\$Ssp_theme
  Start-Sleep -Seconds 2
$shell = New-Object -ComObject WScript.Shell; $shell.AppActivate('Personalization'); $shell.SendKeys('%{F4}')
}
  }
    }
    {$_ -in $winter} {
        if ($reg.CurrentTheme -notlike "*$win_theme_reg*")
{ if (Test-Path c:\temp\$win_theme)
  {   & c:\temp\$win_theme
  Start-Sleep -Seconds 2
$shell = New-Object -ComObject WScript.Shell; $shell.AppActivate('Personalization'); $shell.SendKeys('%{F4}')

      
  }
  else
  {Start-BitsTransfer $win -Destination c:\temp\$win_theme
  & c:\temp\$win_theme
  Start-Sleep -Seconds 2
$shell = New-Object -ComObject WScript.Shell; $shell.AppActivate('Personalization'); $shell.SendKeys('%{F4}')
}
  }
    }
    {$_ -in $Autum} {
    if ($reg.CurrentTheme -notlike "*$aut_theme_reg*")
{ if (Test-Path c:\temp\$aut_theme)
  {   & c:\temp\$aut_theme
  Start-Sleep -Seconds 2
$shell = New-Object -ComObject WScript.Shell; $shell.AppActivate('Personalization'); $shell.SendKeys('%{F4}')

      
  }
  else
  {Start-BitsTransfer $aut -Destination c:\temp\$aut_theme
  & c:\temp\$aut_theme
  Start-Sleep -Seconds 2
$shell = New-Object -ComObject WScript.Shell; $shell.AppActivate('Personalization'); $shell.SendKeys('%{F4}')
}
  }}
  }

Collect users with a certain attribute and clear the attribute.

Last week a college working with identity management needed a script to clear a dummy AD-attribute. All users that don´t have a telephone number needed a dummy phone number because of an external calendar system. This script collects all users with the telephoneNumber attribute 99999 in an variable and then exports the users in the variable with all their attributes before it clears the telephoneNumber attribute. We use it as an scheduled task.

Import-Module Activedirectory
# get date and time tog get the backup unique.
$date = get-date -Format yyyymmddHHmm
# Samla in användare som har 99999 som telefonnummer
# Collect users that hav 99999 as telephonenumber attribute.
$users = Get-ADUser -filter 'telephoneNumber -like "99999"' -Properties *

# Check if backup-file exists, if it exists stop the script and alert the usr, if it not exists export all users that is about to get the attribute changed.
if (Test-Path c:\temp\backup_$date.txt)
{Write-Host  "vänta en minut backupfilen finns redan" -BackgroundColor Yellow -ForegroundColor Red
    
}

else
{
Write-Host "exporterar användare" -BackgroundColor Black -ForegroundColor White
$users | Export-Csv c:\temp\backup_$date.txt -Encoding UTF8 -NoTypeInformation

foreach ($user in $users)
{
Write-Host "Clearing attribute on $user"
  Set-ADUser -Identity $user -Clear telephoneNumber
}
} 

Locate printer driver on multiple computers

printer

A couple of weeks ago a bad printer driver where distributed to some of our clients. It was an universal driver so the impact was on nearly every printer of a specific brand and that brand  represents approximately fifty percent of our printers. We now needed to find out which computers had the faulty driver and to do so we searched every computer if it had a driver with the same driver version(do a get-printerdriver | select * and on property “driverversion” you will find a number) as the bad driver. Since not all of our 8000 clients are powered on or on our network we also exported all computers not answering on Test-Connection to a file that we can use later if we run the script again. After my college had run the script he ended up with two files, one with all computers with bad driver and one with computers not answering to test-connection.

 

$computers = Get-Content "C:\temp\datorer.txt"
$date = get-date -Format HH.mm
$outfile1 = "C:\temp\dosnt_answer $date.txt"
$outfile2 = "C:\temp\wrong_driver $date.txt"

foreach ($computer in $computers)
{

Write-Host "testar om $computer svarar"
$connection = Test-Connection $computer -Count 1 -ErrorAction SilentlyContinue
if ($connection -ne $null)
{Write-Host "$computer answering, checking driver."
$exists = Get-PrinterDriver -ComputerName $computer -ErrorAction SilentlyContinue | where driverversion -eq "1970930661982208"

if ($exists -ne $null)
{write-host "$computer has wrong driver"
Out-File -FilePath "c:\temp\fel_drivare.txt" -Append
    
}
else
{write-host "$computer doesn't have bad driver."
out-file -FilePath $outfile2 -Append -InputObject $computer
}
 
   
}
else
{write-host "$computer doesn´t answer"
out-file -FilePath $outfile1 -Append -InputObject $computer
}


  }

Internet-radio built in powershell

SRLast weekend I had an hour or two over after my kids gone to sleep, to do some scripting at home. I got an idea of building an internet radio in powershell with the four nationwide Swedish public service channels.  I used the .NET framework class System.Windows.Media.MediaPlayer to play the streams and then some variables to feed it. First of you could only choose channel when you ran the function and the only option was quit, but I built in a meny so you will be able to change channel without quiting the function. When I proudly showed the internet radio player to my girlfriend she told me it looked like crap and asked why I just don´t go to the sr.se site…

function SR-Play{
 param 
 ([parameter(mandatory)]
 [ValidateSet("P1","P2","P3","P4")]
 [String]$Radiostation)

 $P1 = "http://sverigesradio.se/topsy/direkt/132-hi-mp3.m3u"
 $P2 = "http://sverigesradio.se/topsy/direkt/2562-hi-mp3.m3u"
 $P3 = "http://sverigesradio.se/topsy/direkt/164-hi-mp3.m3u"
 $P4 = "http://sverigesradio.se/topsy/direkt/179-hi-mp3.m3u"
 
$MediaPlayer = New-Object System.Windows.Media.MediaPlayer 

switch ($Radiostation)
{
    "P1" {$MediaPlayer.Open([uri]$p1)}
    "P2" {$MediaPlayer.Open([uri]$p2)}
    "P3" {$MediaPlayer.Open([uri]$p3)}
    "P4" {$MediaPlayer.Open([uri]$p4)}
}
$MediaPlayer.play()
Start-Sleep -Seconds 1

    do {
    [int]$meny = 0

    while ( $meny -lt 1 -or $meny -gt 9) {
    write-host "Kanalmeny, välj kanal eller avsluta."
    Write-Host "1. P1"
    Write-Host "2. P2"
    Write-Host "3. P3"
    Write-Host "4. P4"
    Write-Host "9. Avsluta"

    [int]$meny = Read-Host "Välj en kanal"

    switch ($meny) {
      1{Write-Host "P1"
      $MediaPlayer.Open([uri]$p1)
$MediaPlayer.play()}
      2{Write-Host "p2"
      $MediaPlayer.Open([uri]$p2)
$MediaPlayer.play()}
      3{Write-Host "p3"
      $MediaPlayer.Open([uri]$p3)
$MediaPlayer.play()}
      4{Write-host "p4"
      $MediaPlayer.Open([uri]$p4)
$MediaPlayer.play()}
      9{Write-Host "Hejdå"}
      default {Write-Host "Fel val"}
    }
  }
  
    }
until ("9" -ccontains $meny
)
 if ($meny -eq "9")
    {$MediaPlayer.stop()
    $MediaPlayer.close()
     }
}