Pythonスクリプトを管理者権限で起動してWindowsのIPConfigを変更する

当サイトではアフィリエイト広告を利用しています

Windowsのネットワーク設定を頻繁に変更することがあって面倒だったのでPythonスクリプトを記述しました。

IPConfigの変更には管理者権限が必要なので、windllでPythonスクリプトを起動し、そこからnetshでIPアドレスを変更するという流れになりました。

yamlで設定を記述できるようにしたので、複数のネットワーク設定があっても気楽に変更できるようになりました。

利用したライブラリ

  • Python 3.11.4
  • subprocess: 標準ライブラリ。コマンド実行に利用
  • ctypes: 標準ライブラリ。Windowsの管理者権限での起動に利用
  • click 8.1.3: コマンドライン引数のパーサーに利用
  • pyyaml 6.0: ネットワーク設定の記述に利用

スクリプト設計

  1. ctypesのwindllを用いてPythonスクリプトを管理者権限で起動
  2. yamlで記述したネットワーク設定を読み込む
  3. 管理者権限のsubprocessでnetshコマンドを実行する

こんな流れで動作してます。

Pythonスクリプト

設定ファイル

以下のように設定したyamlファイルを用意します。gateway,metricは省略可能です。

name: "イーサネット"
address: "192.168.0.10"
netmask: "255.255.255.0"
gateway: "192.168.10.1"
metric: "0"

Configクラス

以下のconfig.pyでネットワーク設定用のdataclassを記述します。

from dataclasses import dataclass
import yaml

@dataclass
class Config:
    name: str  # ex: イーサネット
    address: str
    netmask: str
    gateway: str = None
    metric: str = None

    @classmethod
    def load_yaml(cls, fname):
        with open(fname, "r", encoding="utf-8") as f:
            yml = yaml.safe_load(f)
            cfg = Config(**yml)
            return cfg

ネットワーク設定

以下の_change_cfg.pyではsubprocessによりnetshコマンドを実行し、固定IPアドレスとDHCPの設定を行えます。

この実行には管理者権限が必要となるため、追加のスクリプトを記述します。

なお、clickを用いてサブコマンドで固定・DHCPを指定できるようにしています。

import subprocess
import sys
import click
from lib.config import Config

CMD_PRE = ["netsh", "interface", "ipv4", "set", "address"]

@click.group()
def cli():
    pass

@cli.command()
@click.argument("filename", type=click.Path(exists=True))
def static(filename):
    """
    change ipconfig to static address

    FILENAME: config yaml file of ipconfig
    """
    cfg = Config.load_yaml(filename)
    cmd = CMD_PRE + [f'"{cfg.name}"', "static", cfg.address, cfg.netmask]
    if cfg.gateway is not None and cfg.gateway != "":
        cmd.append(cfg.gateway)
    if cfg.metric is not None and cfg.metric != "":
        cmd.append(cfg.metric)
    print(f"Run command: {' '.join(cmd)}")

    cp = subprocess.run(cmd, capture_output=True, text=True)
    print(f"{cp.stdout}", file=sys.stdout)
    print(f"{cp.stderr}", file=sys.stderr)


@cli.command()
@click.argument("filename", type=click.Path(exists=True))
def dhcp(filename):
    """
    change ipconfig to dhcp address

    FILENAME: config yaml file of ipconfig, use only interface name
    """
    cfg = Config.load_yaml(filename)
    cmd = CMD_PRE + [f'"{cfg.name}"', "dhcp"]
    print(f"Run command: {' '.join(cmd)}")

    cp = subprocess.run(cmd, capture_output=True, text=True)
    print(f"{cp.stdout}", file=sys.stdout)
    print(f"{cp.stderr}", file=sys.stderr)


if __name__ == "__main__":
    cli()

管理者権限スクリプト

run.pyとして以下のように管理者権限での実行スクリプトを用意しました。

こちらもclickでコマンドライン引数をとり、_change_cfg.pyに流すようにしています。

import click
from ctypes import windll

@click.command()
@click.argument("mode", type=click.Choice(["static", "dhcp"], case_sensitive=False))
@click.argument("filename", type=click.Path(exists=True))
def runas(mode, filename):
    """
    IPv4設定変更スクリプトを管理者権限で実行する

    {static|dhcp}: 固定IP設定とするかDHCP設定とするか

    FILENAME: IP設定がかかれたymlファイル。DHCPの場合はインターフェース名のみ読み取る
    """
    _shell = windll.shell32.ShellExecuteW

    return _shell(
        None,
        "runas",
        "python",
        f"_change_cfg.py {mode} {filename}",
        None,
        0,
    )

if __name__ == "__main__":
    runas()

ディレクトリ構成

上記のファイルを以下のように配置します。(__init__.pyは空ファイル)

│  run.py
│  _change_cfg.py
├─config
│      test.yml
└─lib
    │  config.py
    │  __init__.py

使い方

固定アドレスへ書き換える場合は、設定を記述したymlを指定して実行します。

python run.py static config/test.yml

DHCPへ戻す場合は、yamlを指定して記載のインターフェース名に対して設定を行います。

staticと同じファイルを指定すれば、インターフェース名のみ利用してDHCPを有効にします。

python run.py dhcp config/test.yml

設定が正しく反映されたかどうかはnetshコマンドで確認できます。

netsh interface ipv4 show config "イーサネット"

まとめ

  • pythonでWindowsの管理者権限でのスクリプト実行を行った
  • subprocess, ctypes, click, yamlを利用
  • IPアドレスなどネットワーク設定の書き換えで固定IP・DHCPと変更できるようになった