26.06.2020

Windows'a çevrimiçi duvar kağıtları

Şu yazımda Bing'in günlük resimlerinin Fedora'da nasıl duvar kağıdı olarak kullanılabileceğini yazmıştım. Bu aslında internetten bulduğum bir içerikti.

Buradan esinlenerek bu işi Windows'da nasıl yapabilirim diye düşündüm. Powershell'de aynısını yapmaya karar verdim.

Önce gereken değişkenleri tanımlayalım:
$bing="www.bing.com"
$xmlURL="http://www.bing.com/HPImageArchive.aspx?format=xml&idx=0&n=1&mkt=en-US"
$saveDir="D:\wallpapers\"
$picExt=".jpg"
$desiredPicRes="_1440x900"
$fullstop = $false 
$saveDir ile belirtilen klasörün var olduğundan emin olmalıyız. Fedora için yaptığımız gibi var olsun olmasın yaratılması için
New-Item -ItemType Directory -Path $saveDir -ErrorAction SilentlyContinue | Out-Null
Ya da powershell'e özgü Test-Path ile kontrol edip daha sonra yaratmaya gidebiliriz.
if (-Not (Test-Path $saveDir))
{
    New-Item -ItemType Directory _path $saveDir
}
gibi.

Bing'in günlük duvar kağıdı bilgilerini paylaştığı bir xml çevrimiçi verisi var, $xmlURL değişkeni ile verilen adreste. Bu verinin içinde <urlbase> tag'inin içeriğini kullanarak istediğimiz çözünürlükte (varsa) resmi edinebiliriz. Her durumda bu olmayabiliyor. İstdediğimiz çözünürlükte resim yoksa varsayılanı kullanmayı deneyebiliriz.
[xml]$response = (New-Object System.Net.WebClient).DownloadString($xmlURL)

$defaultPicURL = $response.images.image.url
$desiredPicURL = $response.images.image.urlbase + $desiredPicRes + $picExt
<urlbase> tagından elde edilen dosya isimleri şu formatta oluyor:

/th?id=OHR.MontageJupiterIo_EN-US2310290045

Ben bunları şu formatta kaydetmek istiyorum:

MontageJupiterIo_EN-US2310290045_1440x900.jpg

Bunun için de aşağıdaki kırpma işlemlerine tabi tutuyorum:
 
$checkfile = $defaultPicURL.split("&")[0]
$checkfile = $checkfile.split(".")[1] + "." + $checkfile.split(".")[2]
 
Fedora için bu adımları orijinal olarak öneren kişi resimleri günlük olarak tutuyor, yenisini indirince siliyordu. Ben silmemeyi tercih ettim. Ama bu da bir resmin ikinciye indirilme girişimini kontrol etmemi gerektirdi. Bunun için yukarıdaki checkfile değişkeni içinde saklanan dosya adını biraz daha kırpıp dosya uzantısını yok ettim:

$checkfile = $checkfile.substring(0,$checkfile.length-4)

Bu aşamadan sonra bir if bloğunun içinde istenen çözünürlükte dosya var mı diye kontrol ediyorum.
 
if ((dir (Join-Path -Path $saveDir -ChildPath ($checkfile + "*"))).Count -eq 0)
 
Gerçekten bu kadar ayrıntılı bir if koşuluna gerek yoktu aslında, ama benim bu resimleri indirdiğim klasördeki isim karmaşası bunu gerektirdi. Neyse, yukarıdaki if koşulunun doğru olduğu durum için aşağıdaki gibi uzun bir kod var. Burada önce istenen çözünürlük var mı, onu kontrol ediyorum, iwr (Invoke-WebRequest) ile. Sadece dosyanın var olup olmadığını öğrenmek için HTTP HEAD yöntemini kullanması için de -Method parametresi ile head değerini gönderiyorum, ilk try bloğunun içinde. Yoksa iwr'in bir error throw etmesi için de -EA stop kullandım. Catch'in içinde de istenen çöznürlüğün yerine varsayılan çözünürlüğün kullanılmasını sağladım. İç içe iki try-catch ile varsayılan çözünürlükteki resmin de olup olmadığını kontrol ettim. finally bloğunun içinde de bulunan (istenen çözünürlük veya varsayılan) resmin download edilmesini, duvarkağıdı olarak ayarlanmasını sağlayan fonksiyonu çağırdım. Birkaç log tutma satırı ile birlikte.

try {
     $resp = iwr -Uri ($bing + $desiredPicURL) -EA Stop -Method Head

    # desired URL OK
     Write-Host "desired res OK."
    $picName = $desiredPicURL.split(".")[1] + "." +          $desiredPicURL.split(".")[2]
    $fullPath = Join-Path -Path $saveDir -ChildPath $picName
}

catch {
     # desired not available
    try {
        $resp = iwr -Uri ($bing + $defaultPicURL) -EA Stop -Method Head
        Write-Host "desired not found, falling back to default."
        $picName = $defaultPicURL.split("&")[0]
        $picName = $picName.split(".")[1] + "." +                 $picName.split(".")[2]
        $fullPath = Join-Path -Path $saveDir -ChildPath $picName
     }

    catch {
        # neither default available
        Write-Host "None available right now."
        $fullstop = $true
        Break
    }

}

finally {

    if ($fullstop) {
        # here means nothing found
        Add-Content -Path $logfile -Value "$bugun - nothing found."
    }
    else {
        # hear means something is found, either desired or default
        try {
            $resp = iwr -Uri ($bing + $defaultPicURL) -OutFile $fullPath
            Add-Content -Path $logfile -Value "$bugun - downloaded : $fullpath"
            Set-WallPaper $fullpath
        }

    catch {
        $hata = $PSItem.Exception.ToString()
        Write-Host "last stage error: $hata"
        Add-Content -Path $logfile -Value "$bugun - last stage error."
        }
    }
}

 
Bir adım önceki if'e ait else bloğunda da eğer dosya zaten varsa bir daha indirilmemesini ve işlem kaydı düşülmesini sağladım.

else {
    Write-Host "File is already downloaded:" (dir (Join-Path -Path $saveDir -ChildPath ($checkfile + "*")))[0]
    Add-Content -Path $logfile -Value "$bugun - file is already downloaded."
}

Hayatını kodlama ile kazananlar; yukarıdaki karmaşa için kusura bakmayın.

Sonrasında bunu Görev Zamanlayıcısıyla otomatik çalıştırmaya ayarlamak kalıyor. Bunun için Set-ExecutionPolicy ile çalıştırma politikasını uygun şekilde değiştirmek, ps1 dosyasını imzalamak ya da komut satırından powershell.exe ile birlikte -ExecutionPolicy anahtarını kullanmak gerekebilir.

28.06.2020 Düzenleme: Registry'deki anahtarı değiştirip daha sonra rundll32.exe'yi kullanarak UpdatePerUserSystemParameters çağrısı yeni sistemlerde pek işe yaramıyor. Bunun yerine şurada SystemParametersInfo API'sinin kullanılması önerilmiş. Bunu powershell'de nasıl yapabiliriz diye düşünürken önce Matt Graeber'in blog'unda yazdıklarına denk geldim. Çok faydalı olarak PInvoke.com sitesini önermiş. Ama burdan sonra yapılacak bir dünya iş vardı ve ben bu notasyona aşina değildim. Daha fazla aramayla Jose Espitia'nın bloguna denk geldim. Bu işte tam aradığım şeydi. SystemParametersInfo'nun Powershell'den çağrılabilir hale getirilmişi. Bunu kullanarak tam sonuca ulaştım.

Function Set-WallPaper($Image) {
<#
.SYNOPSIS
Applies a specified wallpaper to the current user's desktop
   
.PARAMETER Image
Provide the exact path to the image
.EXAMPLE
Set-WallPaper -Image "C:\Wallpaper\Default.jpg"
#>
Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
public class Params
{
    [DllImport("User32.dll",CharSet=CharSet.Unicode)]
    public static extern int SystemParametersInfo (Int32 uAction,
                                                   Int32 uParam,
                                                   String lpvParam,
                                                   Int32 fuWinIni);
}
'@
$SPI_SETDESKWALLPAPER = 0x0014
$UpdateIniFile = 0x01
$SendChangeEvent = 0x02
$fWinIni = $UpdateIniFile -bor $SendChangeEvent
$ret = [Params]::SystemParametersInfo($SPI_SETDESKWALLPAPER, 0, $Image, $fWinIni)
}

Hiç yorum yok: