A következő címkéjű bejegyzések mutatása: Powershell. Összes bejegyzés megjelenítése
A következő címkéjű bejegyzések mutatása: Powershell. Összes bejegyzés megjelenítése

2021. január 12., kedd

How to find domain users who have never changed their passwords

 Hey, I have not posted here since 2 years! But I'm still kickin hard, and alive. Just to save this blog from being forgotten, placing is a new entry. And... see you in 2 years again. ;)

 How to find users who have never ever freaking changed their passwords? (Those lazy bastards, in spec cases when forced pw change cannot be enabled on them.)

Get-ADUser -Filter * -Properties PasswordLastSet,WhenCreated,lastlogondate | Where-Object {$_.Enabled -eq $true -and $_.Lastlogondate -ne $null -and ( $_.PasswordLastSet.datetime -eq ($_.WhenCreated.datetime) ) }


2018. november 23., péntek

2018. március 8., csütörtök

Powershell applet form

It's a little known fact that it is possible to build a complete application form with pure Powershell. (Being based on .NET.). Here is an awesome example, called login.ps1. This is an applet starts a window that cannot be closed with the regular control buttons (as they are disabled) and asks two data from the user. Then it validates the data and if the data found to be invalid, flashes the input fields with red and returns to the initial state. Yeah, this was the hardest part of the development. If the data are all OK, it flashes the input fields with green color and pushes the data to the event log. This is a very very very special use case and only useful for me, but may help someone out who is just looking for a similar data input solution and gets here with google.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Long Change Answer File Information"
$objForm.Size = New-Object System.Drawing.Size(455,265)
$objForm.StartPosition = "CenterScreen"

# set this to $True to enable "close" button
$objForm.ControlBox = $false
$objForm.KeyPreview = $True

# Add Ok Button
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(180,185)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({
    # some tricky regex to validate our very custom input data
    If( $ChangeTextBox.Text -match "\b([Cc][0-9a-zA-Z]{9}|OST#?[0-9]{6})\b" -And $LoginTextBox.Text -match "\b[a-zA-Z]\w{2,19}\b") # Valid -match "^\d{0,10}$"
    {
        $LoginTextBox.BackColor = "lime";
        $ChangeTextBox.BackColor = "lime";
        Start-Sleep -Milliseconds 600;

        $objForm.Close()
    }
    # we have two input fields and both are required to be validated
    ElseIf (-Not ($LoginTextBox.Text -match "\b[a-zA-Z]\w{2,19}\b") -And ($ChangeTextBox.Text -match "\b([Cc][0-9a-zA-Z]{9}|OST#?[0-9]{6})\b"))   # Invalid
    {
        #$ErrorProvider.SetError($LoginTextBox, "Please enter valid name");
        $LoginTextBox.BackColor = "pink";
        $ChangeTextBox.BackColor = "lime";
    }
    # strange logic but this is the simpliest way in this case, here we get back to reset the form
    ElseIf (-Not ($ChangeTextBox.Text -match "\b([Cc][0-9a-zA-Z]{9}|(OST|ost)#?[0-9]{6})\b") -And ($LoginTextBox.Text -match "\b[a-zA-Z]\w{2,19}\b"))
    {
        $ChangeTextBox.BackColor = "pink";
        $LoginTextBox.BackColor = "lime";
    }
    Else
    {
        # visual warning to the user
        $ChangeTextBox.BackColor = "pink";
        $LoginTextBox.BackColor = "pink";
    }
})
$objForm.Controls.Add($OKButton)   

# Add Textbox Label
$FontBold = new-object System.Drawing.Font("Arial",8,[Drawing.FontStyle]'Bold' )

#UserID label
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(425,20)
$objLabel.Font = $fontBold
$objLabel.text = "Please enter Your UserID"
$objForm.Controls.Add($objLabel)

$objLabel1 = New-Object System.Windows.Forms.Label
$objLabel1.Location = New-Object System.Drawing.Size(30,47)
$objLabel1.Size = New-Object System.Drawing.Size(65,50)
$objLabel1.Text = "UserName:"
$objForm.Controls.Add($objLabel1)

#UserID textbox
$LoginTextBox = New-Object System.Windows.Forms.TextBox
$LoginTextBox.Location = New-Object System.Drawing.Size(120,45)
$LoginTextBox.Size = New-Object System.Drawing.Size(260,20)
#$LoginTextBox.BackColor = "green"
$objForm.Controls.Add($LoginTextBox)

#CH label
$objLabel2 = New-Object System.Windows.Forms.Label
$objLabel2.Location = New-Object System.Drawing.Size(10,100)
$objLabel2.Size = New-Object System.Drawing.Size(425,20)
$objLabel2.Font = $fontBold
$objLabel2.Text = "Please enter change number"
$objForm.Controls.Add($objLabel2)

$objLabel3 = New-Object System.Windows.Forms.Label
$objLabel3.Location = New-Object System.Drawing.Size(30,120)
$objLabel3.Size = New-Object System.Drawing.Size(65,40)
$objLabel3.Text = "CH number:"
$objForm.Controls.Add($objLabel3)

#CH textbox
$ChangeTextBox = New-Object System.Windows.Forms.TextBox
$ChangeTextBox.Location = New-Object System.Drawing.Size(120,120)
$ChangeTextBox.Size = New-Object System.Drawing.Size(260,20)
$objForm.Controls.Add($ChangeTextBox)

# Add Validation Control
$ErrorProvider = New-Object System.Windows.Forms.ErrorProvider

$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()

$change= $ChangeTextBox.TEXT
$login = $LoginTextBox.TEXT

Write-EventLog -Source "Winlogon" -LogName "Application" -EventId 666 -Message "Impersonated user logged as $login for $change implementation"

2018. január 2., kedd

User import from foreign LDAP into own AD - PART2

The script continues with STEP3

$OutFile="C:\quser\ad-userimport-scripts\ujuserekerkeztek.txt"
$LogFile="C:\quser\ad-userimport-scripts\adderlog.txt"
$InFile="C:\quser\ad-userimport-scripts\opslistanevekkel.txt"
$WinUsers = "C:\quser\ad-userimport-scripts\winjumpusers.txt"

# we have some test users which must not be disabled
$ToIgnore = "user1","user2","user3","master1","master2"

$GrA = @() # needed!
$GrA = import-csv $InFile # external LDAP group members
$GrB = gc $WinUsers # external LDAP group members who has additional administrative permission to be imported here
$GrC=(Get-ADGroupMember -identity jumpusers).SamAccountName # members who are already in the local AD

$Gone = $GrC | where {$GrA.uid -notcontains $_ } # members who are already in the local AD but not in the foreign AD - seems like deleted there and already left the team
$ToDelete=(Compare-Object $Gone $ToIgnore).InputObject # generating the user list who are to be deleted locally
# some checks to avoid stupid errors - too short list means we caught only some error message
 $i=@(Get-Content $InFile).Length
 if ( $i  -lt 15 ) {
  write-host "There is something wrong with the list, CHECK IT !" | Out-File $LogFile -Append
  exit 1
  }
    $i=@(Get-Content $WinUsers).Length
 if ( $i  -lt 10 ) {
  write-host "There is something wrong with the list, CHECK IT !" | Out-File $LogFile -Append
  exit 1
  }

# logging
Get-Date | Out-File $LogFile -Append
# handling users who are gone meanwhile from the external LDAP
if ( $ToDelete -ne $null ) {
        $ToDelete | ForEach-Object {
            #Delete-ADaccount -Member $_ -Confirm:$false # delete
            #Remove-ADGroupMember -Identity jumpusers -Member $_ -Confirm:$false # removes from the group
            Disable-ADAccount -identity $_ # disable the user
           Write-Host "DISABLED:" $_ | Out-File $LogFile -Append
        }
    }
# Collecting the users into external data file who are not added yet locally. This is the trickiest part of the script because here we just find the loginID of the user. The first and the last names come from the second list! So the loginID (SAMaccount name) needs to be found in the second list and the realname comes with that from there. 

$result = $GrB | Where {$GrC -NotContains $_}
$GrA.uid|ForEach-Object {
    $uidja = $_
    $ndx = [array]::IndexOf($GrA.uid,$uidja)
    $result|Foreach-Object {
        if ($_ -match $uidja ) {
      $GrA.FirstName[$ndx] $GrA.LastName[$ndx]
         $uidja+","+$GrA.FirstName[$ndx]+","+$GrA.LastName[$ndx] | Out-File $OutFile -Append
            }
     }
    }


#STEP4
# This is where the safe import is happening for the new users. The password is generated locally because that can't be exported from the external LDAP so won't be identical.
[...]


User import from foreign LDAP into own AD - PART1

Here is a rather complex script system I wrote. This is just for myself to remember and record my brilliant thoughts. I doubt if anyone else could use it. The goal is to get my users (including their login names and real names) from an external LDAP system and import them into my AD. (Windows based.) I'm doing the first step by using the ldapsearch from the opensource OpenLDAP package.

# STEP1: the raw list
C:\OpenLDAP\ClientTools\ldapsearch -D "cn=queryuser,dc=admin" -w "$$$$" -h 172.16.16.16 -b "dc=admin" -s sub "(&(objectclass=person)(|(gidnumber=100)(gidnumber=110)))" > C:\quser\ad-userimport-scripts\opslista.txt

# STEP2: an annoying thing here, because in the list we have both Base64 encoded and normal usernames we need to decode only the encoded ones.
$source = Get-Content "C:\quser\ad-userimport-scripts\opslista.txt" | Select-String "cn:", "displayName" #
$OutFile="c:\quser\ad-userimport-scripts\opslistanevekkel.txt"
if (Test-Path $OutFile) { Remove-Item $OutFile }
"uid,FirstName,LastName" > $OutFile
$Name_list = @()
$uid_list = @()

$source|ForEach-Object {
    if ($_ -match "displayName:: ")
              {
              $tem = ($_ -replace "displayName:: ","")
              $tam = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($tem))
            #  $Base64_list += ($_ -replace "displayName:: ","")
           $Name_list += $tam
        }
     elseif ($_ -match "displayName: ")
                {
                $tum = ($_ -replace "displayName: ","")
                 $Name_list += $tum
        }
        }
 
$source|ForEach-Object {
    if ($_ -match "cn: ")
       {
        ($_ -replace "displayName: ","")
        $uid_list += ($_ -replace "cn: ","")
        }
    }

    for($i=0;$i-le $uid_list.length-1;$i++)
        {
       $Name_list[$i]=($Name_list[$i] -replace " ","")
       $uid_list[$i]+","+$Name_list[$i] | Out-File -filepath $OutFile -Append
    }
  



 

2016. június 27., hétfő

File access auditing on a Windows fileserver: Data Leakage Prevention

Here is a clever script concept that helps company managers notifying someone's unusual amount of file reading. That's typical behaviour for an employee who is intended to quit and try to steal all the files of that company. Such auditing softwares are on the market for several hundred or thousand bucks!
Luckily for you, I've written one in bash. OK that's not good news for ones who use only Windows. But it can be easily portable to any script language, for example, php so that it could be run directly in the Windows fileserver or DC by installing the proper runtime enviroment. (PHP, ruby, python, etc.)
Exploring that thought further, now I'm going to translate that for myself. ;) But for now, it's enough to get it work in bash.

The original idea is that we suppose that all the users open almost the same amount of files daily on their daily routines. This script always alerts when a statistical threshold percent reached per user.
In the following example you are going to see a nice solution for lab use in which I transfer the logfile from the Windows server to a Linux server to be able to run the bash script on it. You can find detailed comments inside the script.

Step-by-step installation:
1: Enable audit log policy on your Windows Server, assign it to the target folders and test it
(Note: in the above blog you can find an advanced example. In my case I look for event id 4663 because it just contains the information I need.) Set the audit rules according to your needs. The less eventrule the better. We need to trace file reads so the first rule is a must.


2: You need to export the specific events from the security log to a plain file. So create a getsec.ps1 file in c:\script\ with the following content:
Get-EventLog security -After (Get-Date).AddDays(-1) -InstanceId 4663 |select Message|ft -AutoSize -Wrap > c:\auditing\report.txt
3: Also, don't forget to create that c:\auditing folder and then put an empty file into it named: mounted

 4: Schedule the script to run at the end of the working hours or at midnight. The command is to be: (e.g.) C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe and the argument (e.g.): -executionpolicy bypass -file c:\scripts\getsec.ps1  2>&1 > C:\scripts\log.txt
5: Share c:\auditing folder with a dedicated user that is intended to be used only by the Linux server, e.g.: linuxsrv
6: On your linux box, install the following packages: cifs-utils dos2unix mutt iconv
7: Test your connection:
 [ -f /mnt/mounted ] || mount.cifs //192.168.xx.xx/auditing/ /mnt/ -o username=linuxsrv,password=Sup3rS3cur3P4$$,domain=contoso
8: Create the base directories in, e.g.
mkdir /root/auditor && cd /root/auditor
mkdir archive average stat users; echo "0" > counter

Having succeeded, congratulations, now you are ready to track your file access activity and watch out for possible data stealing FOR FREE!


Here is the mighty script. See comments inline!

2016. június 21., kedd

More Powershell

The original idea was to ease the process of creating a new distribution group with one human member and an archive public folder regularly. These mail enabled security groups and public folders always get their names based on a company standard: Contoso GROUPNAME and Contoso_Groupname_Archive. The most exciting part of it is the waiting loop: we've got to make sure the the new group is created and replicated over the DCs in the domain before going on. Have to be run in an Exchange Shell.
Two minor notes: pfviewer is a special company group in which all the viewer right assigned users are. Jane.manager1 and john.manager2 are the company head managers.

Import-Module activedirectory
$ShName = Read-Host "Please specify the new groupname, e.g.: TechGroup1"
$Name = "Contoso "+$ShName
if (!(dsquery group -samid $Name)){ NEW-ADGroup -name $Name -groupscope 2 -path "OU=ContMailLists,DC=co,DC=local" }else{Write-Host "WARNING: ADGroup already exists. PRESS CTRL+C to exit or take the consequences."}
$DotName ="contoso."+$ShName
$EmailADD = $DotName+"@contoso.com"
$PFName = "Contoso_"+$ShName+"_Archiv"
$Ember = Read-Host "Specify the login name of the user going to be a member of this group. E.g.: john.smith"
$FullPFName = "\"+$PFName
$PFEmail = "contoso"+$ShName+"Archiv@contoso.com"
$IfGroupExists = Get-DistributionGroup -name $Name -ErrorAction 'SilentlyContinue'
  if( $
IfGroupExists)
      {
      $IFSTOP = Read-Host "This distribution group already exists! Press CTRL+C-t to exit"
   }
Write-Host -NoNewline "Please wait a bit. Shouldn't take long"
    Do
    {
        If($Idx -gt 0) {Start-sleep -s 2}
        $r = Get-ADGroup -Filter {SamAccountName -eq $Name}
        Write-Host -NoNewline "."
        $Idx = $Idx + 1
    }
    Until($r)

Enable-DistributionGroup -Identity "CN=$Name,OU=ContMailLists,DC=wt,DC=local" -Alias $DotName
Set-DistributionGroup -Identity $Name -ManagedBy co.local\Admin -BypassSecurityGroupManagerCheck
Set-DistributionGroup -Identity $Name -RequireSenderAuthenticationEnabled 0 -PrimarySmtpAddress $EmailADD -WindowsEmailAddress $EmailADD -EmailAddressPolicyEnabled 0 -Alias $DotName -GrantSendOnBehalfTo jane.manager1, john.manager2, $Ember
New-PublicFolder -Name $PFName -Path \
Enable-MailPublicFolder -Identity $FullPFName -HiddenFromAddresslistsEnabled 1
Set-MailPublicFolder -Identity $FullPFName -EmailAddressPolicyEnabled 0
Set-MailPublicFolder -Identity $FullPFName -EmailAddresses $PFEmail
Add-PublicFolderClientPermission -Identity $FullPFName -accessrights ReadItems,CreateItems,FolderVisible -user pfviewer
Remove-PublicFolderClientPermission -Identity $FullPFName -accessrights ReadItems,EditOwnedItems,DeleteOwnedItems,FolderVisible -user default -Confirm:$false
Add-DistributionGroupMember -Identity $Name -member $PFName
Add-DistributionGroupMember -Identity $Name -member $Ember

2016. január 25., hétfő

Three small scrips

Hey there, long time no see. Nothing special news here, just wanted to see googlebots heading here. So here comes my first new year post, 3 minor scripts. First one is for Exchange: adds new email aliases for users from a .csv and makes it default address.

Import-Csv c:\scripts\data.csv -Header UZER,ADDRZ| Foreach{
Set-Mailbox $_.UZER -EmailAddressPolicyEnabled $false
   $user = Get-Mailbox -Identity $_.UZER
   $user.EmailAddresses+=$_.ADDRZ
   Set-Mailbox $user -PrimarySmtpAddress $_.ADDRZ
}


example of data.csv: (without any headers!)
mgibson,mel.gibson@mighty.com
ctom,tom.cruise@mighty.com
cjoe,joe.cool@mighty.com


Second one is a bit tricky. I wanted to list all my distribution groups and their members. There are lots of solutions for this, e.g. you can find an edifying blog entry here. Unfortunatelly most of these scripts don't work nowadays at Office365 Exchange because of this unpleasing nastiness:
Cannot process argument transformation on parameter 'Identity'. Cannot convert value to type "Microsoft.Exchange.Configuration.Tasks.DistributionGroupMemberIdParameter". Error: "Cannot convert hashtable to an object of the following type: Microsoft.Exchange.Configuration.Tasks.DistributionGroupMemberIdParameter. Hashtable-to-Object conversion is not supported in restricted language mode or a Data section."                                                       
Explained on reddit by PsTakuu: It's not the object being passed into the Get-DistributionGroupMember by the pipeline that is causing the issue, it's that you are shoving an entire object into the first positional parameter (Identity) and it doesn't accept hash tables.
Here's a way to recreate your issue:
Get-DistributionGroup | select -First 1 | %{Get-DistributionGroupMember $_}
Here's the way to fix:
Get-DistributionGroup | select -First 1 | %{Get-DistributionGroupMember $_.identity} 

So here is the final working solution:
foreach ($group in Get-DistributionGroup) { get-distributiongroupmember $group.displayname | ft @{expression={$_.displayname};Label="$group"}}
The results can be redirected to file like this: $( foreach (............) )|out-file file.txt
or
$result = foreach (...)
$result | out-file file.txt -append

A +1 powerlist, for bonus: 
Get-DistributionGroup|format-table -wrap -property name,emailaddresses,hiddenfromaddresslistsenabled,RequireSenderAuthenticationEnabled > c:\groups.txt

 The third supersimple linuxer script adds users to a linux system and into samba fileserver database. I don't care about real names, room numbers and so on. That also creates tricky .bat files to make it easier to attach the network drive to windows users later.

#!/bin/bash
while read line; do
uzer=$(echo $line|cut -d ':' -f1)
pazz=$(echo $line|cut -d ':' -f2)
useradd -p $(openssl passwd -1 $pazz) $uzer --shell /bin/false --no-create-home --no-user-group
echo -ne "$pazz\n$pazz\n" | smbpasswd -a -s $uzer
echo "cmdkey /add:192.168.85.254 /user:workgroup\\$uzer /pass:$pazz" > /root/batz/$uzer.bat
echo "net use m: \\\192.168.85.254\\workz /P:Yes" >> /root/batz/$uzer.bat
done < users.txt

example of users.txt:
melbigson:jydac3sS
tomcruise:hEieafS
joecool:nhi252ax


2015. május 27., szerda

"Verbose" event logging in Windows

The behavior that my Windows 2008 Network Policy Server (aka Radius Server) did not log the successfully authorized usernames always bothered me. Fortunately there is a way to get that stupid habit to work as expected.
Open an elevated command promt and type this to get a list of your event categories and their subcategories:
Auditpol /list /subcategory:* /r  (optional)

Then type: (note that category name strings are localized!)
Auditpol /set /subcategory:"Network Policy Server" /success:enable /failure:enable  
and... backup your policy(ies):
Auditpol /backup /file:C\mypolic.csv  (optional)

Another method to log both Event 6273 and 6279 could be done via a GPO:
Computer Configuration -> Policies -> Windows Settings -> Security Settings -> Advanced Audit Policy Configuration -> Audit Policies -> Logon/Logoff -> Audit Network Policy Server (set both success and failure to enable). Don't forget to gpupdate /force.

Further reading here.

2015. május 20., szerda

Querying Exchange quota limits in Powershell

Get-mailbox | where {$_.UseDatabaseQuotaDefaults -ne $true} 

Get-MailboxDatabase | Get-MailboxStatistics | Where-Object {$_.StorageLimitStatus -match 'BelowLimit|IssueWarning|ProhibitSend|MailboxDisabled'} 

Get-mailbox | get-mailboxstatistics | sort-object totalitemsize –descending | ft displayname,totalitemsize

Get-PublicFolder -Recurse -ResultSize Unlimited  | Get-PublicFolderStatistics -Server <exchange> | Select FolderPath, ItemCount, TotalAssociatedItemSize, TotalDeletedItemSize, TotalItemSize | fl Get-Mailbox -ResultSize Unlimited | Get-MailboxStatistics | Sort-Object TotalItemSize -Descending | Select-Object DisplayName,StorageLimitStatus,TotalItemSize -First 100  | Sort-Object TotalItemSize

Set-Mailbox <mailbox> -UseDatabaseQuotaDefaults $false -IssueWarningQuota 8GB -ProhibitSendQuota 8.5GB


Get-PublicFolder "\" -Recurse -resultsize unlimited| Get-PublicFolderStatistics -Server <server> | Select FolderPath, ItemCount, TotalAssociatedItemSize, TotalDeletedItemSize, TotalItemSize | fl

2015. március 23., hétfő

powershell - check if exists

Taken from various forums... can't remember where from.

do {
$testpath = Test-Path -path \\dns2\d$\test
start-sleep -s 10}
until ($testpath -eq $true)

do {
    sleep -seconds 1
    $mailboxExists = get-mailboxpermission -Identity "CN=$displayName,$DN" -User "NT AUTHORITY\SELF" -ErrorAction SilentlyContinue |fw IsValid
    write-host "." -nonewline
} while (!$mailboxExists)


Import-Module ActiveDirectory
#Import CSV
$csv = @()
$csv = Import-Csv -Path "C:\Temp\bulk_input.csv"

#Get Domain Base
$searchbase = Get-ADDomain | ForEach {  $_.DistinguishedName }

#Loop through all items in the CSV
ForEach ($item In $csv)
{
  #Check if the OU exists
  $check = [ADSI]::Exists("LDAP://$($item.GroupLocation),$($searchbase)")
     If ($check -eq $True)
  {
    Try
    {
      #Check if the Group already exists
      $exists = Get-ADGroup $item.GroupName
      Write-Host "Group $($item.GroupName) alread exists! Group creation skipped!"
    }
    Catch
    {
      #Create the group if it doesn't exist
      $create = New-ADGroup -Name $item.GroupName -GroupScope $item.GroupType -Path ($($item.GroupLocation)+","+$($searchbase))
      Write-Host "Group $($item.GroupName) created!"
    }
  }
  Else
  {
    Write-Host "Target OU can't be found! Group creation skipped!"
  }
}

2015. március 3., kedd

Incremental back up vhdx files of Hyper-V Virtual Machines hosted on Cluster Shared Volumes to a network share

It's 2015 so why would anyone still use Windows Server 2008 R2? Windows Server Backup in Windows Server 2012 includes great (but limited,see below) support for CSV backup. Some notes and warnings:
  •     Virtual machines hosted on CSV’s cannot be added as part of normal system backup configuration
  •     Windows Server Backup has to be configured on all nodes to ensure that backup and recovery will be available in the event of a failure on one of the nodes in the cluster.
  •     Volumes recovery not supported - can be cheated
  •     Security access control lists are not applicable on CSV file service root. Therefore, file recovery to the root of CSV volume is not supported.
Say you have two Hyper-V hosts, one SAN hosting your VMs' files via Cluster Shared Volumes for your Hyper-V Hosts and you also have four VMs on the Hosts - each has two VMs. You already set up your OS level (e.g. Windows Backup) jobs inside your Hosts OSs and VM OSs and has already been backing up your data to the SAN on dedicated backup LUNs lying on physically separeted fault tolarent arrays. Look sufficient, isn't it.
Actually, it isn't that good. What if your SAN blows up? You lost all your VMs and your VMs' backups at the same time. You also need to have a fool-proof off-site backup and it must be easily handled. Luckily, there is a simple solution without the need to include third party tools, like HVbackup. (which is, anyway, a good one)
Let's say that your first Hyper-V Host server called HOST1 and your VMs running by it are named VM1 and VM2.
So you have a file system on Host1 like this:

as C:\ClusterStorage\Volume4\VM1\ ....

To backup your first virtual guest (with its entire CSV, being on the safe side) on your external backup server share, just execute:

wbadmin start backup -include:C:\ClusterStorage\volume4\ -backuptarget:\\backupserver\vmbackup\vm1

It takes some time:

You can easily restore files from your VM's virtual disk if you find it in the backup:
Then just mount it in your Disk Management (Attach VHD) .....
and then assign a drive letter to it, open your new disk with a file explorer and find your real VD :) inside it. You should repeat above process by attaching this real VD also with your Disk Management console.

In case you need to restore your whole VM (whole means: disaster recovery including all its Hyper-V settings)
Find your backup versions: (if you are lucky enough to have more than a single one)

wbadmin get versions -backuptarget:\\backupserver\vmbackup\vm1

Restoring: (be careful)

wbadmin start recovery -version:02/11/2015-08:25 -backuptarget:\\backupserver\vmbackup\vm1 -itemtype:file -items:C:\ClusterStorage\Volume4\  -recursive -recoverytarget:Z\recover -machine:HOST1

What did I mean when I said backup versions? Have you ever been frustrated that Windows Backup can't maintain multiple versions on a network share? So did I. I've tried to cheat WSB with using a local hardlink pointing out to the network share.
mklink /D M:\MyNetwork \\mybackupserver\vmbackup
and
wbadmin start backup -include:C:\ClusterStorage\volume4\ -backuptarget:\\localhost\d$\MyNetwork\vm1 -quiet
Tadaamm! So far so good.

Unfortunately,
wbadmin get versions -backupTarget:M:\MyNetwork                                               
matter-of-factly answers that it can't be fooled in such a stupid way.

wbadmin 1.0 - Backup command-line tool
(C) Copyright 2013 Microsoft Corporation. All rights reserved.
The backup cannot be completed because the backup storage destination is a shared folder mapped to a drive letter. Use the Universal Naming Convention (UNC) path (\\servername\sharename\) of the backup storage destination instead. 
In short, it sadly won't be versioning, just keeps one full version as usual. Bad luck. Folks say I should use iSCSI based network drives because thats the only way to get WSB versioning. I don't want to bother with this because I already have lots of iSCSI drives from the SAN and I would be a bit afraid of messing up these drives from different sources.

Meanwhile, here are some useful facts from Technet topics to consider about WBS: 
You can also set -vssFULL  parameter in backup jobs but there's not much use in doing so. According to the manual: "If specified, performs a full backup using the Volume Shadow Copy Service (VSS). Each file's history is updated to reflect that it was backed up. If this parameter is not used, wbadmin start backup makes a copy backup, but the history of files being backed up is not updated." In short: "vssfull is only meaningful if there is another 3rd party backup application is being simultaneously used on the same machine along with server backup application and you have application like exchange running on the machine who have vss writers. if that is not the case - it can be ignored and defaults will work fine."
And "All backups after first backup automatically takes incremental storage space on the backup location since changes are tracked using volume shadow copy on the backup location. This incremental storage space is proportional to the changes from the last backup."

2015. január 9., péntek

How to flush / empty / purge / resize your System Volume Information folder

vssadmin list shadowstorage
[....]
Shadow Copy Storage association                                                         
For volume: (F:)\\?\Volume{7df38471-635b-11e4-9415-000c29dbe934}\                    
Shadow Copy Storage volume: (F:)\\?\Volume{7df38471-635b-11e4-9415-000c29dbe934}\    
Used Shadow Copy Storage space: 249 GB (41%)                                         
Allocated Shadow Copy Storage space: 251 GB (41%)                                    
Maximum Shadow Copy Storage space: UNBOUNDED (2863325530%)
[...]
                        
vssadmin resize shadowstorage /on=F: /For=F: /Maxsize=40GB 
Successfully resized the shadow copy storage association  

If you want to delete a shadowstorage and encounter the infamous error message:

C:\Users\administrator> vssadmin delete shadowstorage /on=F: /For=F:     
vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool      
(C)Copyright2001-2013MicrosoftCorp.                                                                                                                         
Error: The specified shadow copy storage association is in use.                

then use your superpower:
cmd> vssadmin list shadows
cmd> diskshadow
diskshadow>list shadows all

diskshadow>delete shadows oldest F:
diskshadow>delete shadows volume F:
diskshadow>delete shadows all
diskshadow>list shadows all
diskshadow>exit

2014. október 10., péntek

How to mass-upload profile pictures to Exchange 2013

It's friday afternoon, an hour before the end of your shift. Biggest boss walkes into your office and says:
- It's good to see you being so happy to see me. I've got a job for you. You gotta load our new employees' high-resolution photos into our company's Exchange2013 immediately. You should be able to find all the 180 picture files on the fileserver. Actualy, it should have done yesterday. So... (dramatic pause)... have you done it yet ?
  1. - Don't panic.
  2. - Resize or cut those freaking profile photos to 648x648 pixels with a free smart photo viewer, for example with a batch job in Irfanview.
  3. - Rename the files to match your users' login names, e.g. Bill Gates -- billgates.jpg - That's the trickiest part if your files are called user3412_10102014.png or whatever. That'd suck. Force your colleagues to name the files properly next time.
  4. - Use this script. It reads all the files in the sourcedir, takes the username from filename and puts the picture into that user's corresponding Exchange attribute.

$sourcedir = "c:\temp\photos\"
$files = Get-ChildItem $sourcedir -Filter "*.jpg"
$files | ForEach-Object {

   $fullpath= $sourcedir + $_.Name
   $name = $_.BaseName
   write $fullpath
   write $name

   $photo = ([Byte[]] $(Get-Content -Path $fullpath -Encoding Byte -ReadCount 0))
   Set-UserPhoto -Identity $name -PictureData $photo -Confirm:$False
   Set-UserPhoto -Identity $name -Save -Confirm:$False
  
}


once ready... paste the following into your PS:
Set-OwaVirtualDirectory -ChangePasswordEnabled $true -SetPhotoEnabled $false -DisplayPhotosEnabled $true

Set-CASMailbox user -OWAMailboxPolicy Default 
or
Get-CASMailbox -ResultSize Unlimited | Set-CASMailbox -OWAMailboxPolicy Default

Curious why 648x648 is that dimension exactly? Nobody knows. Frankly. The point is you will end up with 3 different sized picture stored for each account in your AD and Exchange 2013 and Lync 2013 or Sharepoint 2013 (if you have any)
  1. 48 x 48 pixels in AD thumbnailPhoto attribute field (If you upload a photo to Exchange 2013, Exchange will automatically create a 48 pixel by 48 pixel version of that photo and update the user's thumbnailPhoto attribute. Note, however, that the reverse is not true: if you manually update the thumbnailPhoto attribute in Active Directory the photo in the user's Exchange 2013 mailbox will not automatically be updated).
  2. 96 x 96 pixels for use in Microsoft Outlook 2013 Web App and Microsoft Outlook 2013
  3. 648 x 648 pixels for use in Lync 2013 and Sharepoint 2013

2014. január 23., csütörtök

Email forwarding

More powershell fun

Setting forward-only to an internal address :
Set-Mailbox -Identity "Joe Cool" -ForwardingAddress "james@mydomain.com"
Setting deliver-and-forward to an internal address :
Set-Mailbox -Identity "Joe Cool" -ForwardingAddress "james@mydomain.com" -DeliverToMailboxAndForward $true
Setting forward to an external address :
New-MailContact -Name "Very Important Dick" -ExternalEmailAddress "dick@vip.com"
Set-Mailbox "Joe Cool" -ForwardingAddress "dick@vip.com"
Listing what mailboxes are set to forward :
Get-Mailbox -Filter { ForwardingAddress -like '*' } | select-object Name,ForwardingAddress,ForwardingSmtpAddress
Cancel any type of forwarding:
Set-Mailbox -Identity [our.user@mydomain.com] -DeliverToMailboxandforward $False -ForwardingSMTPAddress $Null -ForwardingAddress $Null
Mass canceling:
Get-Mailbox | Where {$_.RecipientType -eq “UserMailbox”} | Set-Mailbox -ForwardingSmtpAddress $null
 

2013. október 18., péntek

Exchange (Public Folder) Powershell tricks (applies to 2010 and 2013)

Say you have a difficult public folder structure and you have to search, for example, quota settings of a folder, in less than five seconds. 
Then do:
get-publicfolder -recurse -resultsize unlimited|where {$_.name -like "First_chars_of_thePF*"}| ft Identity,IssueWarningQuota                                                                                                       

To search based on the email address of the PF (shows lots of details!)
Get-MailPublicFolder "email.address.of.the.PF@yourdomain.com"|fl               

 Modify the quota on the PF:
Set-PublicFolder -identity "\First_level_NAME\Second_level_NAME\3rd_level_NAME" -ProhibitPostQuota 5000MB -IssueWarningQuota 4800MB

Getting some useless data about the PF and its subfolders:              
 Get-PublicFolderStatistics -identity "\First_level_NAME\Second_level_NAME\3rd_level_NAME" -GetChildren| Format-List                                                                

How your databases are called:
Get-MailboxDatabase|ft name,publicfolderdatabase  
 
How your public folders are called:
Get-PublicFolder \ -recurse|ft name,parentpath,replicas

Mailbox
User mailbox size:
Get-MailboxFolderStatistics user.name -FolderScope "Inbox" | Select Name,FolderandSubFolderSize,ItemsinFolderandSubfolders 

More fun on this subject: http://exchangeserverpro.com/reporting-mailbox-folder-sizes-with-powershell/

Yet another awesome collection of Exchange PS commands
http://waynes-world-it.blogspot.com/2013/04/exchange-powershell-commands.html